diff --git a/.gitignore b/.gitignore
index 6cd14d7274ceefe3d802ab464f1b398ced980a47..117c817abe52b602c4bc6f6474d10e57220724f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,10 @@ dist
 *.whl
 *.egg-info
 
+# tests and coverage
+.coverage
+htmlcov
+coverage.xml                
 
 #   mkdocs
 site
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d335f9555db59e60d471769899233b955388802b..cbaa0e6790a2da0ee6f4f8b99986bcc74d14876b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,12 @@
 stages:
-  - pretest
-  - test
+  - "Code Quality"
+  - "Tests"
+  - "Documentation"
   - deploy
 
 linter:
-  stage: pretest
+  stage: "Code Quality"
+  needs: []
   except:
     variables:
       - $ENABLE_NIGHTLY_BUILDS
@@ -15,7 +17,8 @@ linter:
     - docker
 
 typechecker:
-  stage: pretest
+  stage: "Code Quality"
+  needs: []
   except:
     variables:
       - $ENABLE_NIGHTLY_BUILDS
@@ -26,24 +29,46 @@ typechecker:
   tags:
     - docker
 
+testsuite:
+  stage: "Tests"
+  image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
+  needs: []
+  before_script:
+    - pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
+    - pip install -e .
+  script:
+    - pytest -v --cov-report html --cov-report xml --cov-report term --cov=src/pystencilssfg tests
+  artifacts:
+    when: always
+    paths:
+      - htmlcov
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: coverage.xml
+
 build-documentation:
-  stage: test
+  stage: "Documentation"
   image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
+  needs: []
+  before_script:
+    - pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
+    - pip install -e .[docs]
   script:
-    - pip install mkdocs mkdocs-material mkdocstrings[python]
-    - mkdocs build
+    - cd docs
+    - make html
   tags:
     - docker
   artifacts:
     paths:
-      - site
+      - docs/build/html
 
 pages:
   image: i10git.cs.fau.de:5005/pycodegen/pycodegen/full
   stage: deploy
   script:
     - ls -l
-    - mv site public  # folder has to be named "public" for gitlab to publish it
+    - mv docs/build/html public  # folder has to be named "public" for gitlab to publish it
   artifacts:
     paths:
       - public
diff --git a/COPYING.txt b/COPYING.txt
new file mode 100644
index 0000000000000000000000000000000000000000..be3f7b28e564e7dd05eaf59d64adba1a4065ac0e
--- /dev/null
+++ b/COPYING.txt
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d0c3cbf1020d5c292abdedf27627c6abe25e2293
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/api/composer.md b/docs/api/composer.md
deleted file mode 100644
index c0542aab02a9881a7a1776990d15ec5eb0e1992c..0000000000000000000000000000000000000000
--- a/docs/api/composer.md
+++ /dev/null
@@ -1,8 +0,0 @@
-
-::: pystencilssfg.composer.SfgComposer
-
-::: pystencilssfg.composer.SfgBasicComposer
-
-::: pystencilssfg.composer.SfgClassComposer
-
-::: pystencilssfg.composer.make_sequence
\ No newline at end of file
diff --git a/docs/api/context.md b/docs/api/context.md
deleted file mode 100644
index fcc494885cb57515c265c2ef6bcbda158c776dab..0000000000000000000000000000000000000000
--- a/docs/api/context.md
+++ /dev/null
@@ -1,2 +0,0 @@
-
-::: pystencilssfg.context.SfgContext
diff --git a/docs/api/cpp_std.md b/docs/api/cpp_std.md
deleted file mode 100644
index bfd9aaa5fc69984b804b3099ad8b91a46cc841ff..0000000000000000000000000000000000000000
--- a/docs/api/cpp_std.md
+++ /dev/null
@@ -1,8 +0,0 @@
-
-::: pystencilssfg.source_concepts.cpp.std_vector_ref
-
-::: pystencilssfg.source_concepts.cpp.mdspan_ref
-
-::: pystencilssfg.source_concepts.cpp.StdVector
-
-::: pystencilssfg.source_concepts.cpp.StdMdspan
diff --git a/docs/api/emission.md b/docs/api/emission.md
deleted file mode 100644
index fa6211ee2cfa0bcd6cea4ae86f3745ded44b2af7..0000000000000000000000000000000000000000
--- a/docs/api/emission.md
+++ /dev/null
@@ -1,14 +0,0 @@
-
-## Output Configuration
-
-::: pystencilssfg.configuration.SfgOutputSpec
-
-## Header-Implementation-Pair Emission
-
-::: pystencilssfg.emission.HeaderImplPairEmitter
-
-## Code Style and `clang-format`
-
-::: pystencilssfg.configuration.SfgCodeStyle
-
-::: pystencilssfg.emission.clang_format.invoke_clang_format
\ No newline at end of file
diff --git a/docs/api/generator.md b/docs/api/generator.md
deleted file mode 100644
index 1b2b77f1ac0fbc1572a67bdd708d644fcfdf7a75..0000000000000000000000000000000000000000
--- a/docs/api/generator.md
+++ /dev/null
@@ -1,6 +0,0 @@
-
-::: pystencilssfg.generator.SourceFileGenerator
-
-::: pystencilssfg.configuration.SfgConfiguration
-
-::: pystencilssfg.configuration.DEFAULT_CONFIG
diff --git a/docs/api/index.md b/docs/api/index.md
deleted file mode 100644
index d3c9905d887aee50dd3c06ba5ec10cfbd0de56cc..0000000000000000000000000000000000000000
--- a/docs/api/index.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# API Documentation
-
-These pages document the public API of *pystencils-sfg*.
-
-### Front End
-
- - [Source File Generator](generator.md)
- - [Code Generation Context](context.md)
- - [Composer](composer.md)
-
-### Source File Modelling
-
- - [Source File Components](source_components.md)
- - [Kernel Call Tree](tree.md)
-
-### High-Level Language Concepts
-
- - [Base Classes](source_objects.md)
- - [C++ Standard Library](cpp_std.md)
-
-### Code Generation
-
- - [Emission and Printing](emission.md)
\ No newline at end of file
diff --git a/docs/api/source_components.md b/docs/api/source_components.md
deleted file mode 100644
index 29448be166e2c21dfebd2a50efee14ec3245b7d4..0000000000000000000000000000000000000000
--- a/docs/api/source_components.md
+++ /dev/null
@@ -1,36 +0,0 @@
-
-## Kernels and Kernel Namespaces
-
-::: pystencilssfg.source_components.SfgKernelNamespace
-
-::: pystencilssfg.source_components.SfgKernelHandle
-
-## Includes
-
-::: pystencilssfg.source_components.SfgHeaderInclude
-
-## Functions
-
-::: pystencilssfg.source_components.SfgFunction
-
-## Classes
-
-::: pystencilssfg.source_components.SfgClassKeyword
-
-::: pystencilssfg.source_components.SfgClass
-
-### Visibility
-
-::: pystencilssfg.source_components.SfgVisibility
-
-::: pystencilssfg.source_components.SfgVisibilityBlock
-
-### Members
-
-::: pystencilssfg.source_components.SfgClassMember
-
-::: pystencilssfg.source_components.SfgInClassDefinition
-
-::: pystencilssfg.source_components.SfgConstructor
-
-::: pystencilssfg.source_components.SfgMethod
diff --git a/docs/api/source_objects.md b/docs/api/source_objects.md
deleted file mode 100644
index 2ba6d4ff3bd7c9fe8aa0d3d43e23bb2811680572..0000000000000000000000000000000000000000
--- a/docs/api/source_objects.md
+++ /dev/null
@@ -1,6 +0,0 @@
-
-::: pystencilssfg.source_concepts.SrcObject
-
-::: pystencilssfg.source_concepts.SrcField
-
-::: pystencilssfg.source_concepts.SrcVector
diff --git a/docs/api/tree.md b/docs/api/tree.md
deleted file mode 100644
index c50e4dee760a7fc861673a89f4779f347e21bca0..0000000000000000000000000000000000000000
--- a/docs/api/tree.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
-## Base Classes
-
-::: pystencilssfg.tree.SfgCallTreeNode
-
-::: pystencilssfg.tree.SfgCallTreeLeaf
-
-::: pystencilssfg.tree.SfgEmptyNode
-
-## Utility Nodes
-
-::: pystencilssfg.tree.SfgFunctionParams
-
-::: pystencilssfg.tree.SfgRequireIncludes
-
-## Sequences of Statements
-
-::: pystencilssfg.tree.SfgSequence
-
-::: pystencilssfg.tree.SfgStatements
-
-## Structural and Conditional Constructs
-
-::: pystencilssfg.tree.SfgBlock
-
-## Kernel Calls
-
-::: pystencilssfg.tree.SfgKernelCallNode
-
-
diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css
deleted file mode 100644
index c84bd345a3a1188fc523936ad437bf97021c0809..0000000000000000000000000000000000000000
--- a/docs/css/mkdocstrings.css
+++ /dev/null
@@ -1,35 +0,0 @@
-
-h2.doc-heading {
-    font-size: x-large
-}
-
-h3.doc-heading {
-    font-size: large;
-}
-
-.doc-class>.doc-heading::before {
-    font-size: small;
-    content: "class ";
-    margin-right: 5pt;
-}
-
-.doc-contents {
-    border-left: 3pt solid rgb(60, 60, 60);
-    padding-left: 10pt;
-}
-
-.doc-class .doc-children .doc-attribute>.doc-heading::before {
-    font-size: small;
-    content: "attribute ";
-    margin-right: 5pt;
-}
-
-.doc-class .doc-children .doc-function>.doc-heading::before {
-    font-size: small;
-    content: "function ";
-    margin-right: 5pt;
-}
-
-.doc-children {
-    padding-left: 10pt;
-}
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index e5315634cfe19f81de2a3a3ad70772ee8bc2025e..0000000000000000000000000000000000000000
--- a/docs/index.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# The pystencils Source File Generator
-
-[![](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/pipeline.svg)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/commits/master)
-[![](https://img.shields.io/gitlab/license/pycodegen%2Fpystencils-sfg?gitlab_url=https%3A%2F%2Fi10git.cs.fau.de)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/-/blob/master/LICENSE)
-
-A bridge over the semantic gap between code emitted by [pystencils](https://pypi.org/project/pystencils/)
-and your C/C++/Cuda/HIP framework.
-
-## Installation
-
-### From Git
-
-Install the package into your current Python environment from the git repository using pip
-(usage of virtual environments is strongly encouraged!):
-
-```bash
-pip install git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git
-```
-
-### From PyPI
-
-Not yet available.
-
-## Primer
-
-With *pystencils-sfg*, including your *pystencils*-generated kernels with handwritten code becomes straightforward
-and intuitive. To illustrate, generating a Jacobi smoother for the two-dimensional Poisson equation
-and mapping it onto C++23 `std::mdspan`s takes just a few lines of code:
-
-```python
-import sympy as sp
-
-from pystencils import fields, kernel
-
-from pystencilssfg import SourceFileGenerator, SfgComposer
-from pystencilssfg.source_concepts.cpp import mdspan_ref
-
-with SourceFileGenerator() as ctx:
-    sfg = SfgComposer(ctx)
-
-    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
-    h = sp.Symbol("h")
-
-    @kernel
-    def poisson_jacobi():
-        u_dst[0,0] @= (h**2 * f[0, 0] + u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
-
-    poisson_kernel = sfg.kernels.create(poisson_jacobi)
-
-    sfg.function("jacobi_smooth")(
-        sfg.map_field(u_src, mdspan_ref(u_src)),
-        sfg.map_field(u_dst, mdspan_ref(u_dst)),
-        sfg.map_field(f, mdspan_ref(f)),
-        sfg.call(poisson_kernel)
-    )
-```
-
-Take this code, store it into a file `poisson_smoother.py`, and enter the magic words into a terminal:
-
-```shell
-python poisson_smoother.py
-```
-
-This command will execute the code generator through the `SourceFileGenerator` context manager.
-The code generator takes the name of your Python script, replaces `.py` with `.cpp` and `.h`, and writes
-`poisson_smoother.cpp` and `poisson_smoother.h` into the current directory, ready to be `#include`d.
-
-The above is what we call a *generator script*; a Python script that, when executed, produces a pair
-of source files of the same name, but with different extensions.
-Generator scripts are the primary front-end pattern of *pystencils-sfg*; to learn more about them,
-read the [Usage Guide](usage/generator_scripts.md).
-
-## CMake Integration
-
-*Pystencils-sfg* comes with a CMake module to register generator scripts for on-the-fly code generation.
-With the module loaded, use the function `pystencilssfg_generate_target_sources` inside your `CMakeLists.txt`
-to register one or multiple generator scripts; their outputs will automatically be added to the specified target.
-
-```CMake
-pystencilssfg_generate_target_sources( <target name> 
-    SCRIPTS kernels.py ...
-    FILE_EXTENSIONS .h .cpp
-)
-```
-
-*Pystencils-sfg* makes sure that all generated files are on the project's include path.
-To `#include` them, add the prefix `gen/<target name>`:
-
-```C++
-#include "gen/<target name>/kernels.h"
-```
-
-For details on how to add *pystencils-sfg* to your CMake project, refer to
-[CLI and Build System Integration](usage/cli_and_build_system.md).
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000000000000000000000000000000000000..747ffb7b3033659bdd2d1e6eae41ecb00358a45e
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/api/composer.rst b/docs/source/api/composer.rst
new file mode 100644
index 0000000000000000000000000000000000000000..75acd8abedc7ebe0c9c9024575f3bffe99f7c6bf
--- /dev/null
+++ b/docs/source/api/composer.rst
@@ -0,0 +1,17 @@
+***************************************
+Composer API (`pystencilssfg.composer`)
+***************************************
+
+.. autoclass:: pystencilssfg.composer.SfgComposer
+    :members:
+
+.. autoclass:: pystencilssfg.composer.SfgIComposer
+    :members:
+
+.. autoclass:: pystencilssfg.composer.SfgBasicComposer
+    :members:
+
+.. autoclass:: pystencilssfg.composer.SfgClassComposer
+    :members:
+
+.. autofunction:: pystencilssfg.composer.make_sequence
diff --git a/docs/source/api/generation.rst b/docs/source/api/generation.rst
new file mode 100644
index 0000000000000000000000000000000000000000..45065c13edc444239e928ecb66a376bc700e7959
--- /dev/null
+++ b/docs/source/api/generation.rst
@@ -0,0 +1,11 @@
+**************************
+Generator Script Interface
+**************************
+
+.. autoclass:: pystencilssfg.SourceFileGenerator
+    :members:
+
+.. autoclass:: pystencilssfg.SfgConfiguration
+    :members:
+
+.. autoattribute:: pystencilssfg.configuration.DEFAULT_CONFIG
diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..681b6e851fffa26d94785dbdd71765953d6ba343
--- /dev/null
+++ b/docs/source/api/index.rst
@@ -0,0 +1,13 @@
+#############
+API Reference
+#############
+
+These pages provide a reference for the public API of *pystencils-sfg*.
+
+.. toctree::
+    :maxdepth: 1
+    
+    generation
+    composer
+    lang
+    ir
diff --git a/docs/source/api/ir.rst b/docs/source/api/ir.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a9451f46c239b73b32e1f8a79b8a166d753ed8e6
--- /dev/null
+++ b/docs/source/api/ir.rst
@@ -0,0 +1,8 @@
+Internal Code Representation (`pystencilssfg.ir`)
+=================================================
+
+.. autoclass:: pystencilssfg.SfgContext
+    :members:
+
+.. automodule:: pystencilssfg.ir
+    :members:
diff --git a/docs/source/api/lang.rst b/docs/source/api/lang.rst
new file mode 100644
index 0000000000000000000000000000000000000000..efc69d98c3b278a3fa34b5164a35f58f76c6e2d5
--- /dev/null
+++ b/docs/source/api/lang.rst
@@ -0,0 +1,25 @@
+Language Modelling (`pystencilssfg.lang`)
+=========================================
+
+.. automodule:: pystencilssfg.lang
+
+Expressions
+-----------
+
+.. automodule:: pystencilssfg.lang.expressions
+    :members:
+
+C++ Standard Library (`pystencilssfg.lang.cpp`)
+-----------------------------------------------
+
+Quick Access
+^^^^^^^^^^^^
+
+.. automodule:: pystencilssfg.lang.cpp.std
+    :members:
+
+Implementation
+^^^^^^^^^^^^^^
+
+.. automodule:: pystencilssfg.lang.cpp
+    :members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..8a8d28d25178ddcdcbd34c962e9a7cca554e0319
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,71 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "pystencils-sfg"
+copyright = "2024, Frederik Hennig"
+author = "Frederik Hennig"
+release = "0.0a"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+    "myst_parser",
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+    "sphinx.ext.intersphinx",
+    "sphinx_autodoc_typehints",
+    "sphinx_design",
+    "sphinx_copybutton"
+]
+
+templates_path = ["_templates"]
+exclude_patterns = []
+source_suffix = {
+    ".rst": "restructuredtext",
+    ".md": "markdown",
+}
+master_doc = "index"
+nitpicky = True
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "furo"
+# html_static_path = ['_static']
+
+#   Intersphinx
+
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3.8", None),
+    "numpy": ("https://docs.scipy.org/doc/numpy/", None),
+    "matplotlib": ("https://matplotlib.org/", None),
+    "sympy": ("https://docs.sympy.org/latest/", None),
+}
+
+
+#   Autodoc options
+
+autodoc_member_order = "bysource"
+autodoc_typehints = "description"
+
+
+#   Prepare code generation examples
+
+def build_examples():
+    import subprocess
+    import os
+
+    examples_dir = os.path.join("usage", "examples",)
+
+    subprocess.run(["python", "build.py"], cwd=examples_dir).check_returncode()
+
+
+print("Generating output of example scripts...")
+build_examples()
diff --git a/docs/source/index.md b/docs/source/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..5f74297c9213c091444caccbf85f366adaf4cb28
--- /dev/null
+++ b/docs/source/index.md
@@ -0,0 +1,212 @@
+# The pystencils Source File Generator
+
+```{toctree}
+:maxdepth: 1
+:hidden:
+
+usage/index
+api/index
+```
+
+[![](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/badges/master/pipeline.svg)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/commits/master)
+[![](https://img.shields.io/gitlab/license/pycodegen%2Fpystencils-sfg?gitlab_url=https%3A%2F%2Fi10git.cs.fau.de)](https://i10git.cs.fau.de/pycodegen/pystencils-sfg/-/blob/master/LICENSE)
+
+A bridge over the semantic gap between code emitted by [pystencils](https://pypi.org/project/pystencils/)
+and your C/C++/Cuda/HIP framework.
+
+## Installation
+
+### From Git
+
+Install the package into your current Python environment from the git repository using pip
+(usage of virtual environments is strongly encouraged!):
+
+```bash
+pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils-sfg.git"
+```
+
+````{caution}
+
+*pystencils-sfg* requires *pystencils 2.0* and is not compatible with *pystencils 1.3.x*.
+However, *pystencils 2.0* is still under development and only available as a pre-release version.
+To use *pystencils-sfg*, explicitly install *pystencils* from the v2.0 development branch:
+   
+```bash
+pip install "git+https://i10git.cs.fau.de/pycodegen/pystencils.git@v2.0-dev"
+```
+````
+
+### From PyPI
+
+Not yet available.
+
+## Primer
+
+With *pystencils-sfg*, including your *pystencils*-generated kernels with handwritten code becomes straightforward
+and intuitive. To illustrate, generating a Jacobi smoother for the two-dimensional Poisson equation
+and mapping it onto C++23 `std::mdspan`s takes just a few lines of code:
+
+```python
+import sympy as sp
+
+from pystencils import fields, kernel
+
+from pystencilssfg import SourceFileGenerator
+from pystencilssfg.lang.cpp import mdspan_ref
+
+with SourceFileGenerator() as sfg:
+    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
+    h = sp.Symbol("h")
+
+    @kernel
+    def poisson_jacobi():
+        u_dst[0,0] @= (h**2 * f[0, 0] + u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
+
+    poisson_kernel = sfg.kernels.create(poisson_jacobi)
+
+    sfg.function("jacobi_smooth")(
+        sfg.map_field(u_src, mdspan_ref(u_src)),
+        sfg.map_field(u_dst, mdspan_ref(u_dst)),
+        sfg.map_field(f, mdspan_ref(f)),
+        sfg.call(poisson_kernel)
+    )
+```
+
+The script above, and the code within the region controlled by the `SourceFileGenerator`,
+constructs a C++ header/implementation file pair by describing its contents.
+We first describe our Jacobi smoother symbolically using *pystencils*
+and then pass it to the `sfg` to add it to the output file.
+Then, a wrapper function `jacobi_smooth` is defined which maps the symbolic fields onto `std::mdspan`
+objects and then executes the kernel.
+
+Take this code, store it into a file `poisson_smoother.py`, and execute the script from a terminal:
+
+```shell
+python poisson_smoother.py
+```
+
+During execution, *pystencils-sfg* assembles the above constructs into an internal representation of the C++ files.
+It then takes the name of your Python script, replaces `.py` with `.cpp` and `.h`,
+and exports the constructed code to the files 
+`poisson_smoother.cpp` and `poisson_smoother.h` into the current directory, ready to be `#include`d.
+
+````{dropdown} poisson_smoother.h
+
+```C++
+#pragma once
+
+#include <cstdint>
+#include <experimental/mdspan>
+
+#define RESTRICT __restrict__
+
+void jacobi_smooth(
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent, 1>> &f,
+    const double h,
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_dst,
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_src
+);
+```
+
+````
+
+````{dropdown} poisson_smoother.cpp
+
+```C++
+#include "poisson_smoother.h"
+
+#include <math.h>
+
+#define FUNC_PREFIX inline
+
+/*************************************************************************************
+ *                                Kernels
+ *************************************************************************************/
+
+namespace kernels {
+
+FUNC_PREFIX void kernel(const int64_t _size_f_0, const int64_t _size_f_1,
+                        const int64_t _stride_f_0, const int64_t _stride_f_1,
+                        const int64_t _stride_u_dst_0,
+                        const int64_t _stride_u_dst_1,
+                        const int64_t _stride_u_src_0,
+                        const int64_t _stride_u_src_1, double *const f_data,
+                        const double h, double *const u_dst_data,
+                        double *const u_src_data) {
+  const double __c_1_0o4_0 = 1.0 / 4.0;
+  for (int64_t ctr_1 = 1LL; ctr_1 < _size_f_1 - 1LL; ctr_1 += 1LL) {
+    for (int64_t ctr_0 = 1LL; ctr_0 < _size_f_0 - 1LL; ctr_0 += 1LL) {
+      u_dst_data[ctr_0 * _stride_u_dst_0 + ctr_1 * _stride_u_dst_1] =
+          __c_1_0o4_0 * u_src_data[(ctr_0 + 1LL) * _stride_u_src_0 +
+                                   ctr_1 * _stride_u_src_1] +
+          __c_1_0o4_0 * u_src_data[ctr_0 * _stride_u_src_0 +
+                                   (ctr_1 + 1LL) * _stride_u_src_1] +
+          __c_1_0o4_0 * u_src_data[ctr_0 * _stride_u_src_0 +
+                                   (ctr_1 + -1LL) * _stride_u_src_1] +
+          __c_1_0o4_0 * u_src_data[(ctr_0 + -1LL) * _stride_u_src_0 +
+                                   ctr_1 * _stride_u_src_1] +
+          __c_1_0o4_0 * (h * h) *
+              f_data[ctr_0 * _stride_f_0 + ctr_1 * _stride_f_1];
+    }
+  }
+}
+
+} // namespace kernels
+
+/*************************************************************************************
+ *                                Functions
+ *************************************************************************************/
+
+void jacobi_smooth(
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent, 1>> &f,
+    const double h,
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_dst,
+    std::mdspan<double, std::extents<uint64_t, std::dynamic_extent, std::dynamic_extent>> &u_src) 
+{
+  double *const u_src_data{u_src.data_handle()};
+  const int64_t _stride_u_src_0{u_src.stride(0)};
+  const int64_t _stride_u_src_1{u_src.stride(1)};
+  double *const u_dst_data{u_dst.data_handle()};
+  const int64_t _stride_u_dst_0{u_dst.stride(0)};
+  const int64_t _stride_u_dst_1{u_dst.stride(1)};
+  double *const f_data{f.data_handle()};
+  const int64_t _size_f_0{f.extents().extent(0)};
+  const int64_t _size_f_1{f.extents().extent(1)};
+  /* f.extents().extent(2) == 1 */
+  const int64_t _stride_f_0{f.stride(0)};
+  const int64_t _stride_f_1{f.stride(1)};
+  kernels::kernel(_size_f_0, _size_f_1, _stride_f_0, _stride_f_1,
+                  _stride_u_dst_0, _stride_u_dst_1, _stride_u_src_0,
+                  _stride_u_src_1, f_data, h, u_dst_data, u_src_data);
+}
+```
+
+````
+
+The above is what we call a *generator script*; a Python script that, when executed, produces a pair
+of source files of the same name, but with different extensions.
+Generator scripts are the primary front-end pattern of *pystencils-sfg*; to learn more about them,
+read the [Usage Guide](usage/generator_scripts.md).
+
+## CMake Integration
+
+*Pystencils-sfg* comes with a CMake module to register generator scripts for on-the-fly code generation.
+With the module loaded, use the function `pystencilssfg_generate_target_sources` inside your `CMakeLists.txt`
+to register one or multiple generator scripts; their outputs will automatically be added to the specified target.
+
+```CMake
+pystencilssfg_generate_target_sources( <target name> 
+    SCRIPTS kernels.py ...
+    FILE_EXTENSIONS .h .cpp
+)
+```
+
+*Pystencils-sfg* makes sure that all generated files are on the project's include path.
+To `#include` them, add the prefix `gen/<target name>`:
+
+```C++
+#include "gen/<target name>/kernels.h"
+```
+
+For details on how to add *pystencils-sfg* to your CMake project, refer to
+[CLI and Build System Integration](usage/cli_and_build_system.md).
diff --git a/docs/usage/cli_and_build_system.md b/docs/source/usage/cli_and_build_system.md
similarity index 98%
rename from docs/usage/cli_and_build_system.md
rename to docs/source/usage/cli_and_build_system.md
index 05f38d70af50888cef8bacab8f4ce5124aa76af7..910b6b630e4f7b4501dd2f4ca0958042f4d43eda 100644
--- a/docs/usage/cli_and_build_system.md
+++ b/docs/source/usage/cli_and_build_system.md
@@ -1,3 +1,5 @@
+(guide:cli)=
+# CLI and Build System
 
 ## Command Line Interface
 
diff --git a/docs/source/usage/examples/.gitignore b/docs/source/usage/examples/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..26a5736a94d1260044161ac4963b05b3f3407025
--- /dev/null
+++ b/docs/source/usage/examples/.gitignore
@@ -0,0 +1,2 @@
+*.cpp
+*.h
\ No newline at end of file
diff --git a/docs/source/usage/examples/build.py b/docs/source/usage/examples/build.py
new file mode 100644
index 0000000000000000000000000000000000000000..4726887436c7949167d60f50139b165354d80306
--- /dev/null
+++ b/docs/source/usage/examples/build.py
@@ -0,0 +1,22 @@
+import os
+import glob
+import subprocess
+
+GROUPS = ["guide_generator_scripts"]
+THIS_DIR = os.path.split(__file__)[0]
+
+
+def main():
+    for group in GROUPS:
+        group_folder = os.path.join(THIS_DIR, group)
+
+        for group_entry in os.listdir(group_folder):
+            scripts_dir = os.path.join(group_folder, group_entry)
+            if os.path.isdir(scripts_dir):
+                query = os.path.join(scripts_dir, "*.py")
+                for script in glob.glob(query):
+                    subprocess.run(["python", script], cwd=scripts_dir).check_returncode()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/docs/source/usage/examples/guide_generator_scripts/01/kernels.py b/docs/source/usage/examples/guide_generator_scripts/01/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..a23e622ffd334f082f067602c037450790e5f636
--- /dev/null
+++ b/docs/source/usage/examples/guide_generator_scripts/01/kernels.py
@@ -0,0 +1,4 @@
+from pystencilssfg import SourceFileGenerator
+
+with SourceFileGenerator() as sfg:
+    pass
diff --git a/docs/source/usage/examples/guide_generator_scripts/02/kernels.py b/docs/source/usage/examples/guide_generator_scripts/02/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..e897f1dd2b672ac24fa15ef2c5f0c8eac3a0abb6
--- /dev/null
+++ b/docs/source/usage/examples/guide_generator_scripts/02/kernels.py
@@ -0,0 +1,6 @@
+from pystencilssfg import SourceFileGenerator
+
+with SourceFileGenerator() as sfg:
+    sfg.include("<vector>")
+    sfg.include("<span>")
+    sfg.include("custom_header.hpp")
diff --git a/docs/source/usage/examples/guide_generator_scripts/03/kernels.py b/docs/source/usage/examples/guide_generator_scripts/03/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..83fad64e80b3dad5cf62dede158df25eeeccc83e
--- /dev/null
+++ b/docs/source/usage/examples/guide_generator_scripts/03/kernels.py
@@ -0,0 +1,16 @@
+from pystencilssfg import SourceFileGenerator
+
+import pystencils as ps
+import sympy as sp
+
+with SourceFileGenerator() as sfg:
+    #   Define a copy kernel
+    src, dst = ps.fields("src, dst: [1D]")
+    c = sp.Symbol("c")
+
+    @ps.kernel
+    def scale():
+        dst.center @= c * src.center()
+
+    #   Add it to the file
+    scale_kernel = sfg.kernels.create(scale, "scale")
diff --git a/docs/source/usage/examples/guide_generator_scripts/04/kernels.py b/docs/source/usage/examples/guide_generator_scripts/04/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec26dd14abb801d79edd9c08abff943953c58cac
--- /dev/null
+++ b/docs/source/usage/examples/guide_generator_scripts/04/kernels.py
@@ -0,0 +1,23 @@
+from pystencilssfg import SourceFileGenerator
+
+import pystencils as ps
+import sympy as sp
+
+with SourceFileGenerator() as sfg:
+    #   Define a copy kernel
+    src, dst = ps.fields("src, dst: [1D]")
+    c = sp.Symbol("c")
+
+    @ps.kernel
+    def scale():
+        dst.center @= c * src.center()
+
+    #   Add it to the file
+    scale_kernel = sfg.kernels.create(scale, "scale")
+
+    #   start
+    #   ... see above ...
+    sfg.function("scale_kernel")(
+        sfg.call(scale_kernel)
+    )
+    #   end
diff --git a/docs/source/usage/examples/guide_generator_scripts/05/kernels.py b/docs/source/usage/examples/guide_generator_scripts/05/kernels.py
new file mode 100644
index 0000000000000000000000000000000000000000..431c4cf365a52ca4f9504a6c43aa3c7ebce3d535
--- /dev/null
+++ b/docs/source/usage/examples/guide_generator_scripts/05/kernels.py
@@ -0,0 +1,28 @@
+from pystencilssfg import SourceFileGenerator
+
+import pystencils as ps
+import sympy as sp
+
+with SourceFileGenerator() as sfg:
+    #   Define a copy kernel
+    src, dst = ps.fields("src, dst: [1D]")
+    c = sp.Symbol("c")
+
+    @ps.kernel
+    def scale():
+        dst.center @= c * src.center()
+
+    #   Add it to the file
+    scale_kernel = sfg.kernels.create(scale, "scale")
+
+    #   start
+    import pystencilssfg.lang.cpp.std as std
+
+    sfg.include("<span>")
+    
+    sfg.function("scale_kernel")(
+        sfg.map_field(src, std.vector(src)),
+        sfg.map_field(dst, std.span(dst)),
+        sfg.call(scale_kernel)
+    )
+    #   end
diff --git a/docs/source/usage/generator_scripts.md b/docs/source/usage/generator_scripts.md
new file mode 100644
index 0000000000000000000000000000000000000000..57b9bcdf3e2aa56a942da0d884265cd669d445cc
--- /dev/null
+++ b/docs/source/usage/generator_scripts.md
@@ -0,0 +1,194 @@
+(guide:generator_scripts)=
+# Generator Scripts
+
+Writing generator scripts is the primary usage idiom of *pystencils-sfg*.
+A generator script is a Python script, say `kernels.py`, which contains *pystencils-sfg*
+code at the top level that, when executed, emits source code to a pair of files `kernels.h`
+and `kernels.cpp`. This guide describes how to write such a generator script, its structure, and how
+it can be used to generate code.
+
+## Anatomy
+
+The code generation process in a generator script is controlled by the `SourceFileGenerator` context manager.
+It configures the code generator by combining configuration options from the 
+environment (e.g. a CMake build system) with options specified in the script,
+and infers the names of the output files from the script's name.
+It then returns a {py:class}`composer <pystencilssfg.composer.SfgComposer>` to the user,
+which provides a convenient interface for constructing the source files.
+
+To start, place the following code in a Python script, e.g. `kernels.py`:
+
+```{literalinclude} examples/guide_generator_scripts/01/kernels.py
+```
+
+The source file is constructed within the context manager's managed region.
+During execution of the script, when the region ends, a header/source file pair
+`kernels.h` and `kernels.cpp` will be written to disk next to your script.
+Execute the script as-is and inspect the generated files, which will of course still be empty:
+
+``````{dropdown} Generated Files
+`````{tab-set}
+
+````{tab-item} kernels.h
+```{literalinclude} examples/guide_generator_scripts/01/kernels.h
+```
+````
+
+````{tab-item} kernels.cpp
+```{literalinclude} examples/guide_generator_scripts/01/kernels.cpp
+```
+````
+`````
+``````
+
+<!-- A few notes on configuration:
+
+ - The [SourceFileGenerator](#pystencilssfg.SourceFileGenerator) parses the script's command line arguments
+   for configuration options (refer to [CLI and Build System Integration](cli_and_build_system.md)).
+   If you intend to evaluate command-line parameters inside your
+   generator script, read them from `sfg.context.argv` instead of `sys.argv`.
+   There, all arguments meant for the code generator are already removed.
+ - The code generator's configuration is consolidated from a global project configuration which may
+   be provided by the build system; a number of command line arguments; and the
+   [SfgConfiguration](#pystencilssfg.SfgConfiguration) provided in the script.
+   The project configuration may safely be overridden by the latter two; however, conflicts
+   between command-line arguments and the configuration defined in the script will cause
+   an exception to be thrown. -->
+
+## Using the Composer
+
+The object `sfg` constructed in above snippet is an instance of [SfgComposer](#pystencilssfg.composer.SfgComposer).
+The composer is the central part of the user front-end of *pystencils-sfg*.
+It provides an interface for constructing source files that closely mimics
+C++ syntactic structures within Python.
+Here is an overview of its various functions:
+
+### Includes and Definitions
+
+With [`SfgComposer.include`](#pystencilssfg.composer.SfgBasicComposer.include), the code generator can be instructed to include header files.
+As in C++, you can use the `<>` delimiters for system headers, and omit them for project headers.
+
+`````{tab-set}
+
+````{tab-item} kernels.py
+```{literalinclude} examples/guide_generator_scripts/02/kernels.py
+```
+````
+
+````{tab-item} kernels.h
+```{literalinclude} examples/guide_generator_scripts/02/kernels.h
+```
+````
+
+````{tab-item} kernels.cpp
+```{literalinclude} examples/guide_generator_scripts/02/kernels.cpp
+```
+````
+`````
+
+### Adding Kernels
+
+[pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/)-generated kernels are managed in *kernel namespaces*.
+The default kernel namespace is called `kernels` and is available via
+[`sfg.kernels`](#pystencilssfg.composer.SfgBasicComposer.kernels).
+Adding an existing *pystencils* AST, or creating one from a list of assignments, is possible through 
+[`kernels.add`](#pystencilssfg.ir.SfgKernelNamespace.add)
+and
+[`kernels.create`](#pystencilssfg.ir.SfgKernelNamespace.create).
+The latter is a wrapper around
+[`pystencils.create_kernel`](
+https://pycodegen.pages.i10git.cs.fau.de/pystencils/sphinx/kernel_compile_and_call.html#pystencils.create_kernel
+).
+Both functions return a [kernel handle](#pystencilssfg.ir.SfgKernelHandle)
+through which the kernel can be accessed, e.g. for calling it in a function.
+
+To access other kernel namespaces than the default one,
+the [`sfg.kernel_namespace`](#pystencilssfg.composer.SfgBasicComposer.kernel_namespace) method can be used.
+
+`````{tab-set}
+
+````{tab-item} kernels.py
+```{literalinclude} examples/guide_generator_scripts/03/kernels.py
+```
+````
+
+````{tab-item} kernels.h
+```{literalinclude} examples/guide_generator_scripts/03/kernels.h
+```
+````
+
+````{tab-item} kernels.cpp
+```{literalinclude} examples/guide_generator_scripts/03/kernels.cpp
+```
+````
+`````
+
+### Building Functions
+
+Through the composer, you can define free functions in your generated C++ file.
+These may contain arbitrary code;
+their primary intended task however is to wrap kernel calls with the necessary boilerplate code
+to integrate them into a framework.
+The composer provides an interface for constructing functions that tries to mimic the look of the generated C++ code.
+Use `sfg.function` to create a function, and `sfg.call` to call a kernel:
+
+`````{tab-set}
+
+````{tab-item} kernels.py
+```{literalinclude} examples/guide_generator_scripts/04/kernels.py
+:start-after: start
+:end-before: end
+```
+````
+
+````{tab-item} kernels.h
+```{literalinclude} examples/guide_generator_scripts/04/kernels.h
+```
+````
+
+````{tab-item} kernels.cpp
+```{literalinclude} examples/guide_generator_scripts/04/kernels.cpp
+```
+````
+`````
+
+Note the special syntax: To mimic the look of a C++ function, the composer uses a sequence of two calls
+to construct the function.
+
+The function body can furthermore be populated with code to embedd the generated kernel into
+the target C++ application.
+If you examine the generated files of the previous example, you will notice that your
+function `scale_kernel` has lots of raw pointers and integer indices in its interface.
+We can wrap those up into proper C++ data structures,
+such as, for example, `std::span` or `std::vector`, like this:
+
+`````{tab-set}
+
+````{tab-item} kernels.py
+```{literalinclude} examples/guide_generator_scripts/05/kernels.py
+:start-after: start
+:end-before: end
+```
+````
+
+````{tab-item} kernels.h
+```{literalinclude} examples/guide_generator_scripts/05/kernels.h
+```
+````
+
+````{tab-item} kernels.cpp
+```{literalinclude} examples/guide_generator_scripts/05/kernels.cpp
+```
+````
+`````
+
+If you now inspect the generated code, you will see that the interface of your function is
+considerably simplified.
+Also, all the necessary code was added to its body to extract the low-level information required
+by the actual kernel from the data structures.
+
+The `sfg.map_field` API can be used to map pystencils fields to a variety of different data structures.
+The pystencils-sfg provides modelling support for a number of C++ standard library classes
+(see {any}`pystencilssfg.lang.cpp.std`).
+It also provides the necessary infrastructure for modelling the data structures of any C++ framework
+in a similar manner.
diff --git a/docs/source/usage/index.md b/docs/source/usage/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..5783e9ebe71faade1b69dd233595eb8dcb3710ce
--- /dev/null
+++ b/docs/source/usage/index.md
@@ -0,0 +1,38 @@
+# Usage Guides
+
+```{toctree}
+:maxdepth: 1
+:hidden:
+
+generator_scripts
+cli_and_build_system
+tips_n_tricks
+```
+
+These pages provide an overview of how to use the pystencils Source File Generator.
+A basic understanding of [pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/index.html)
+is required.
+
+```{card} Writing Generator Scripts
+:link: guide:generator_scripts
+:link-type: ref
+
+Learn about *generator scripts*, the primary usage idiom of *pystencils-sfg*:
+Embedd *pystencils*-generated kernels into C++ source files and augment them with
+arbitrary C++ glue code.
+```
+
+```{card} CLI and Build System Integration
+:link: guide:cli
+:link-type: ref
+
+Learn how to control code generation from the command line
+and how to embedd *pystencils-sfg* into your build system.
+```
+
+```{card} Tips and Tricks
+:link: guide:tips_n_tricks
+:link-type: ref
+
+A collection of various tricks that might come in handy when working with *pystencils-sfg*.
+```
diff --git a/docs/usage/tips_n_tricks.md b/docs/source/usage/tips_n_tricks.md
similarity index 95%
rename from docs/usage/tips_n_tricks.md
rename to docs/source/usage/tips_n_tricks.md
index 06c74d6af6931a5d4773122a8dd06c7a2e8756cb..43b2d388e4cb6dae8c5641adef250f529b72997c 100644
--- a/docs/usage/tips_n_tricks.md
+++ b/docs/source/usage/tips_n_tricks.md
@@ -1,3 +1,5 @@
+(guide:tips_n_tricks)=
+# Tips and Tricks
 
 ## Make CLion treat generated files as project sources
 
diff --git a/docs/usage/building.md b/docs/usage/building.md
deleted file mode 100644
index 01d129500861dfe25b9bb18152d2ac73842a004e..0000000000000000000000000000000000000000
--- a/docs/usage/building.md
+++ /dev/null
@@ -1,30 +0,0 @@
-
-## Namespaces
-
-Conceptually, there exist two different kinds of namespaces: *kernel namespaces* for the generated kernels,
-and a single *code namespace* for all the generated code.
-Both get mapped to standard C++ namespaces, in the end, but they fulfill different purposes in the code generator.
-
-*Kernel namespaces* are used for grouping generated kernels together, e.g. to avoid name collisions.
-If, for example, a code generation script combines kernels and functions produced by different components, each
-component may create its own kernel namespace to isolate its kernels.
-
-The *code namespace*, in contrast, envelops all the generated code. Its fully qualified name is built from two parts:
-
- - The *outer namespace* is defined in the [generator configuration][pystencilssfg.SfgConfiguration], typically by
-   the global project configuration;
- - The *inner namespace* is defined by the code generation script, e.g. via [`SfgComposer.namespace`][pystencilssfg.SfgComposer.namespace].
-
-These namespaces will finally occur in the generated implementation file as:
-
-```C++
-namespace outer_namespace::inner_namespace {
-
-namespace kernels {
-    /* kernel definitions */
-} // namespace kernels
-
-/* function definitions */
-
-} // namespace outer_namespace::inner_namespace
-```
diff --git a/docs/usage/generator_scripts.md b/docs/usage/generator_scripts.md
deleted file mode 100644
index 25f2bd4d1b8fd8664d177e56b22f00e9aca719a2..0000000000000000000000000000000000000000
--- a/docs/usage/generator_scripts.md
+++ /dev/null
@@ -1,186 +0,0 @@
-
-Generator scripts are the primary way *pystencils-sfg* is meant to be used.
-A generator script is a single Python script, say `kernels.py`, which contains *pystencils-sfg*
-code at the top level such that, when executed, it emits source code to a pair of files `kernels.h`
-and `kernels.cpp`. This guide describes how to write such a generator script, its structure, and how
-it can be used to generate code.
-
-This page gives a general overview over the code generation process, but introduces only the
-convenient high-level interface provided by the [SourceFileGenerator][pystencilssfg.SourceFileGenerator]
-and [SfgComposer][pystencilssfg.SfgComposer] classes.
-For a more in-depth look into building source files, and about using *pystencils-sfg* outside
-of a generator script, please take a look at the [In-Depth Guide](building.md).
-
-## Anatomy
-
-The code generation process in a generator script is controlled by the
-[SourceFileGenerator][pystencilssfg.SourceFileGenerator] context manager.
-It configures the code generator by combining configuration options from the 
-environment (e.g. a CMake build system) with options specified in the script,
-and infers the names of the output files from the script's name.
-It then prepares and returns a code generation [context][pystencilssfg.SfgContext].
-This context may then be passed to a [composer][pystencilssfg.SfgComposer],
-which provides a convenient interface for constructing the source files.
-
-To start, place the following code in a Python script, e.g. `kernels.py`:
-
-```Python
-from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgComposer
-
-sfg_config = SfgConfiguration()
-with SourceFileGenerator(sfg_config) as ctx:
-    sfg = SfgComposer(ctx)
-
-```
-
-The source file is constructed within the context manager's managed region.
-During execution of the script, when the region ends, a header/source file pair
-`kernels.h` and `kernels.cpp` will be written to the file system next to your script.
-Execute the script as-is and inspect the generated files, which will of course
-still be empty.
-
-A few notes on configuration:
-
- - The [SourceFileGenerator][pystencilssfg.SourceFileGenerator] parses the script's command line arguments
-   for configuration options (refer to [CLI and Build System Integration](cli_and_build_system.md)).
-   If you intend to use command-line parameters in your
-   generation script, use [`sfg.context.argv`][pystencilssfg.SfgContext.argv] instead of `sys.argv`.
-   There, all arguments meant for the code generator are already removed.
- - The code generator's configuration is consolidated from a global project configuration which may
-   be provided by the build system; a number of command line arguments; and the
-   [SfgConfiguration][pystencilssfg.SfgConfiguration] provided in the script.
-   The project configuration may safely be overridden by the latter two; however, conflicts
-   between command-line arguments and the configuration defined in the script will cause
-   an exception to be thrown.
-
-## Using the Composer
-
-The object `sfg` constructed in above snippet is an instance of [SfgComposer][pystencilssfg.SfgComposer].
-The composer is the central part of the user front-end of *pystencils-sfg*.
-It provides an interface for constructing source files that attempts to closely mimic
-C++ syntactic structures within Python.
-Here is an overview of its various functions:
-
-### Includes and Definitions
-
-With [`SfgComposer.include`][pystencilssfg.SfgComposer.include], the code generator can be instructed
-to include header files. 
-
-```Python
-with SourceFileGenerator(sfg_config) as ctx:
-    sfg = SfgComposer(ctx)
-    # ...
-    sfg.include("<vector>")
-    sfg.incldue("custom_header.h")
-```
-
-### Adding Kernels
-
-`pystencils`-generated kernels are managed in
-[kernel namespaces][pystencilssfg.source_components.SfgKernelNamespace].
-The default kernel namespace is called `kernels` and is available via
-[`SfgComposer.kernels`][pystencilssfg.SfgComposer.kernels].
-Adding an existing `pystencils` AST, or creating one from a list of assignments, is possible
-through [`add`][pystencilssfg.source_components.SfgKernelNamespace.add]
-and [`create`][pystencilssfg.source_components.SfgKernelNamespace.create].
-The latter is a wrapper around
-[`pystencils.create_kernel`](
-https://pycodegen.pages.i10git.cs.fau.de/pystencils/sphinx/kernel_compile_and_call.html#pystencils.create_kernel
-).
-Both functions return a [kernel handle][pystencilssfg.source_components.SfgKernelHandle]
-through which the kernel can be accessed, e.g. for calling it in a function.
-
-If required, use [`SfgComposer.kernel_namespace`][pystencilssfg.SfgComposer.kernel_namespace]
-to access other kernel namespaces than the default one.
-
-```Python
-with SourceFileGenerator(sfg_config) as ctx:
-    sfg = SfgComposer(ctx)
-    # ...
-
-    ast = ps.create_kernel(assignments, config)
-    khandle = sfg.kernels.add(ast, "kernel_a")
-    
-    # is equivalent to
-    
-    khandle = sfg.kernels.create(assignments, "kernel_a", config)
-
-    # You may use a different namespace
-    nspace = sfg.kernel_namespace("group_of_kernels")
-    nspace.create(assignments, "kernel_a", config)
-```
-
-### Building Functions
-
-[Functions][pystencilssfg.source_components.SfgFunction] form the link between your `pystencils` kernels
-and your C++ framework. A function in *pystencils-sfg* translates to a simple C++ function, and should
-fulfill just the following tasks:
-
- - Extract kernel parameters (pointers, sizes, strides, numerical coefficients)
-   from C++ objects (like fields, vectors, other data containers)
- - Call one or more kernels in sequence or in conditional branches
-
-It is the philosophy of this project that anything more complicated than this should happen in handwritten
-code; these generated functions are merely meant to close the remaining gap.
-
-The composer provides an interface for constructing functions that tries to mimic the look of the generated C++
-code.
-Use [`SfgComposer.function`][pystencilssfg.SfgComposer.function] to create a function,
-and [`SfgComposer.call`][pystencilssfg.SfgComposer.call] to call a kernel by its handle:
-
-```Python
-with SourceFileGenerator(sfg_config) as ctx:
-    sfg = SfgComposer(ctx)
-    # ...
-
-    sfg.function("MyFunction")(
-        sfg.call(khandle)
-    )
-```
-
-Note the special syntax: To mimic the look of a C++ function, the composer uses a sequence of two calls
-to construct the function.
-
-The function body may further be populated with the following things:
-
-#### Parameter Mappings
-
-Extract kernel parameters from C++ objects:
-
- - [`map_param`][pystencilssfg.SfgComposer.map_param]: Add a single line of code to define one parameter
-   depending on one other.
- - [`map_field`][pystencilssfg.SfgComposer.map_field] maps a pystencils
-   [`Field`](https://pycodegen.pages.i10git.cs.fau.de/pystencils/sphinx/field.html)
-   to a field data structure providing the necessary pointers, sizes and stride information.
-   The field data structure must be provided as an instance of a subclass of
-   [`SrcField`][pystencilssfg.source_concepts.SrcField].
-   Currently, *pystencils-sfg* provides mappings to 
-   [`std::vector`](https://en.cppreference.com/w/cpp/container/vector)
-   (via [`std_vector_ref`][pystencilssfg.source_concepts.cpp.std_vector_ref])
-   and
-   [`std::mdspan`](https://en.cppreference.com/w/cpp/container/mdspan)
-   (via [`mdspan_ref`][pystencilssfg.source_concepts.cpp.mdspan_ref])
-   from the C++ standard library.
- - [`map_vector`][pystencilssfg.SfgComposer.map_vector] maps a sequence of scalar numerical values
-   (given as `pystencils.TypedSymbol`s) to a vector data type. Currently, only `std::vector` is provided.
-
-#### Conditional Branches
-
-A conditonal branch may be added with [`SfgComposer.branch`][pystencilssfg.SfgComposer.branch]
-using a special syntax:
-
-```Python
-with SourceFileGenerator(sfg_config) as ctx:
-    sfg = SfgComposer(ctx)
-    # ...
-    
-    sfg.function("myFunction")(
-        # ...
-        sfg.branch("condition")(
-            # then-body
-        )(
-            # else-body (may be omitted)
-        )
-    )
-    
-```
\ No newline at end of file
diff --git a/docs/usage/index.md b/docs/usage/index.md
deleted file mode 100644
index d4ceba027d6705188463f46a553698983ab04906..0000000000000000000000000000000000000000
--- a/docs/usage/index.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# User Guides
-
-These pages provide an overview of how to use the pystencils Source File Generator.
-A basic understanding of [pystencils](https://pycodegen.pages.i10git.cs.fau.de/pystencils/index.html)
-is required.
-
-## Guides
-
- - [Writing Generator Scripts](generator_scripts.md) explains about the primary interface of *pystencils-sfg*:
-   Generator scripts, which are Python scripts that, when executed, emit *pystencils*-generated code to a header/source
-   file pair with the same name as the script.
- - [In-Depth: Building Source Files](building.md)
- - [CLI and Build System Integration](cli_and_build_system.md)
- - [Tips And Tricks](tips_n_tricks.md): A collection of various tricks that might come in handy when working with *pystencils-sfg*.
\ No newline at end of file
diff --git a/integration/MakeDemo/kernels.py b/integration/MakeDemo/kernels.py
index 43c0f858cdd6b9bf58566fcad342713d48e81394..87f2920b5c788a5031ddbe67a71e788a980183c7 100644
--- a/integration/MakeDemo/kernels.py
+++ b/integration/MakeDemo/kernels.py
@@ -5,7 +5,7 @@ import sympy as sp
 from pystencils import fields, kernel
 
 from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgComposer
-from pystencilssfg.source_concepts.cpp import mdspan_ref
+from pystencilssfg.lang.cpp import mdspan_ref
 
 sfg_config = SfgConfiguration(
     outer_namespace="make_demo"
@@ -22,7 +22,7 @@ Author: Frederik Hennig <frederik.hennig@fau.de>""")
     
     sfg.namespace("jacobi")
 
-    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
+    u_src, u_dst, f = fields("u_src, u_dst, f : double[2D]", layout="fzyx")
     h = sp.Symbol("h")
 
     @kernel
diff --git a/integration/TestSequencing.py b/integration/TestSequencing.py
index 3a0e040bfb0be6d97c1e5896ee0a201c905ec466..b25593a114082ab419de632b48478b0770f079b6 100644
--- a/integration/TestSequencing.py
+++ b/integration/TestSequencing.py
@@ -9,13 +9,11 @@ with SourceFileGenerator() as ctx:
     lb_config = LBMConfig(streaming_pattern='esotwist')
 
     lb_ast_even = create_lb_ast(lbm_config=lb_config, timestep=Timestep.EVEN)
-    lb_ast_even.function_name = "streamCollide_even"
 
     lb_ast_odd = create_lb_ast(lbm_config=lb_config, timestep=Timestep.ODD)
-    lb_ast_odd.function_name = "streamCollide_odd"
 
-    kernel_even = sfg.kernels.add(lb_ast_even)
-    kernel_odd = sfg.kernels.add(lb_ast_odd)
+    kernel_even = sfg.kernels.add(lb_ast_even, "lb_even")
+    kernel_odd = sfg.kernels.add(lb_ast_odd, "lb_odd")
 
     sfg.function("myFunction")(
         sfg.branch("(timestep & 1) ^ 1")(
diff --git a/integration/test_class_composer.py b/integration/test_class_composer.py
index 07039b7473b0b2cc7b9ead2eaa2af2e5d2dbdaf6..2a028b0e48175dc950798006fdf02ace1f408a9a 100644
--- a/integration/test_class_composer.py
+++ b/integration/test_class_composer.py
@@ -2,7 +2,6 @@
 from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgComposer
 from pystencilssfg.configuration import SfgCodeStyle
 from pystencilssfg.composer import SfgClassComposer
-from pystencilssfg.source_concepts import SrcObject
 
 from pystencils import fields, kernel
 
@@ -19,7 +18,6 @@ f, g = fields("f, g(1): double[2D]")
 
 with SourceFileGenerator(sfg_config) as ctx:
     sfg = SfgComposer(ctx)
-    c = SfgClassComposer(ctx)
 
     @kernel
     def assignments():
@@ -27,29 +25,29 @@ with SourceFileGenerator(sfg_config) as ctx:
 
     khandle = sfg.kernels.create(assignments)
 
-    c.struct("DataStruct")(
-        SrcObject("coord", "uint32_t"),
-        SrcObject("value", "float")
+    sfg.struct("DataStruct")(
+        sfg.var("coord", "uint32_t"),
+        sfg.var("value", "float")
     ),
 
-    c.klass("MyClass", bases=("MyBaseClass",))(
+    sfg.klass("MyClass", bases=("MyBaseClass",))(
         # class body sequencer
 
-        c.constructor(SrcObject("a", "int"))
+        sfg.constructor(sfg.var("a", "int"))
         .init("a_(a)")
         .body(
             'cout << "Hi!" << endl;'
         ),
 
-        c.private(
-            c.var("a_", "int"),
+        sfg.private(
+            sfg.var("a_", "int"),
 
-            c.method("getX", returns="int")(
+            sfg.method("getX", returns="int")(
                 "return 2.0;"
             )
         ),
 
-        c.public(
+        sfg.public(
             "using xtype = uint8_t;"
         )
     )
diff --git a/integration/test_classes.py b/integration/test_classes.py
index 7db94d2db5edf6d1a20450c9346a9ec92fd6fb7e..4be345197965ae8b15353f3ce0471e265d28b35d 100644
--- a/integration/test_classes.py
+++ b/integration/test_classes.py
@@ -1,9 +1,8 @@
 # type: ignore
 from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgComposer
 from pystencilssfg.configuration import SfgCodeStyle
-from pystencilssfg.types import SrcType
-from pystencilssfg.source_concepts import SrcObject
-from pystencilssfg.source_components import SfgClass, SfgMemberVariable, SfgConstructor, SfgMethod, SfgVisibility
+from pystencils.types import PsCustomType
+from pystencilssfg.ir.source_components import SfgClass, SfgMemberVariable, SfgConstructor, SfgMethod
 
 from pystencils import fields, kernel
 
@@ -38,7 +37,7 @@ with SourceFileGenerator(sfg_config) as ctx:
         sfg.seq(
             "return -1.0;"
         ),
-        return_type=SrcType("double"),
+        return_type="double",
         inline=True,
         const=True
     ))
@@ -48,20 +47,20 @@ with SourceFileGenerator(sfg_config) as ctx:
         sfg.seq(
             "return 2.0f;"
         ),
-        return_type=SrcType("float"),
+        return_type="float",
         inline=False,
         const=True
     ))
 
     cls.default.append_member(
         SfgMemberVariable(
-            "stuff", "std::vector< int >"
+            "stuff", PsCustomType("std::vector< int > &")
         )
     )
 
     cls.default.append_member(
         SfgConstructor(
-            [SrcObject("stuff", "std::vector< int > &")],
+            [sfg.var("stuff", PsCustomType("std::vector< int > &"))],
             ["stuff_(stuff)"]
         )
     )
diff --git a/integration/test_cuda.py b/integration/test_cuda.py
new file mode 100644
index 0000000000000000000000000000000000000000..56a478c7b08e684fb03398b5b7cb951982336fe8
--- /dev/null
+++ b/integration/test_cuda.py
@@ -0,0 +1,20 @@
+from pystencils import Target, CreateKernelConfig, create_kernel, no_jit
+from lbmpy import create_lb_update_rule, LBMOptimisation
+from pystencilssfg import SourceFileGenerator, SfgConfiguration
+from pystencilssfg.lang.cpp import mdspan_ref
+
+sfg_config = SfgConfiguration(
+    output_directory="out/test_cuda",
+    outer_namespace="gen_code",
+    impl_extension="cu"
+)
+
+with SourceFileGenerator(sfg_config) as sfg:
+    gen_config = CreateKernelConfig(target=Target.CUDA, jit=no_jit)
+    opt = LBMOptimisation(field_layout="fzyx")
+    update = create_lb_update_rule()
+    kernel = sfg.kernels.create(update, "lbm_update", gen_config)
+
+    sfg.function("lb_update")(
+        sfg.call(kernel)
+    )
diff --git a/integration/test_sycl.py b/integration/test_sycl.py
new file mode 100644
index 0000000000000000000000000000000000000000..d49edc71bbf8c10afa57bb7abbe9240043f00c5b
--- /dev/null
+++ b/integration/test_sycl.py
@@ -0,0 +1,21 @@
+from pystencils import Target, CreateKernelConfig, create_kernel, no_jit
+from lbmpy import create_lb_update_rule, LBMOptimisation
+from pystencilssfg import SourceFileGenerator, SfgConfiguration, SfgOutputMode
+from pystencilssfg.lang.cpp import mdspan_ref
+
+sfg_config = SfgConfiguration(
+    output_directory="out/test_sycl",
+    outer_namespace="gen_code",
+    impl_extension="ipp",
+    output_mode=SfgOutputMode.INLINE
+)
+
+with SourceFileGenerator(sfg_config) as sfg:
+    gen_config = CreateKernelConfig(target=Target.SYCL, jit=no_jit)
+    opt = LBMOptimisation(field_layout="fzyx")
+    update = create_lb_update_rule()
+    kernel = sfg.kernels.create(update, "lbm_update", gen_config)
+
+    sfg.function("lb_update")(
+        sfg.call(kernel)
+    )
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 86f75a5ba6a83cceb8f070c212c0f81565ecc308..0000000000000000000000000000000000000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-site_name: pystencils Source File Generator Documentation
-site_author: Frederik Hennig
-copyright: © 2023 Frederik Hennig
-
-repo_name: GitLab
-repo_url: https://i10git.cs.fau.de/pycodegen/pystencils-sfg
-
-
-theme: 
-  name: material
-  features: 
-    - navigation.tabs
-    - content.code.copy
-  palette:
-    scheme: slate
-    primary: deep purple
-
-extra_css:
- - css/mkdocstrings.css
-
-plugins:
-  - search
-  - autorefs
-  - mkdocstrings:
-      default_handler: python
-      handlers:
-        python:
-          paths: [src]
-          options:
-            heading_level: 2
-            members_order: source
-            group_by_category: False
-            show_root_heading: True
-            show_root_full_path: True
-            show_symbol_type_heading: True
-            show_symbol_type_toc: True
-            show_source: False
-            show_signature_annotations: True
-            signature_crossrefs: True
-
-markdown_extensions:
-  - pymdownx.highlight:
-      anchor_linenums: true
-  - pymdownx.superfences 
-
-nav:
-  - Home: index.md
-  - 'User Guides':
-    - 'Overview': usage/index.md
-    - 'Writing Generator Scripts': usage/generator_scripts.md
-    - 'In-Depth: Building Source Files': usage/building.md
-    - 'CLI and Build System Integration': usage/cli_and_build_system.md
-    - 'Tips and Tricks': usage/tips_n_tricks.md
-  - 'API Documentation':
-    - 'Overview': api/index.md
-    - 'Front End':
-      - 'Source File Generator': api/generator.md
-      - 'Code Generation Context': api/context.md
-      - 'Composer': api/composer.md
-    - 'Source File Modelling':
-      - 'Source File Components': api/source_components.md
-      - 'Kernel Call Tree': api/tree.md
-    - 'High-Level Language Concepts':
-      - 'Base Classes': 'api/source_objects.md'
-      - 'C++ Standard Library': 'api/cpp_std.md'
-    - 'Code Generation':
-      - 'Emission and Printing': api/emission.md
diff --git a/pdm.lock b/pdm.lock
deleted file mode 100644
index 2334d1622c09b5b837895ae626a248f71f46b03b..0000000000000000000000000000000000000000
--- a/pdm.lock
+++ /dev/null
@@ -1,1011 +0,0 @@
-# This file is @generated by PDM.
-# It is not intended for manual editing.
-
-[metadata]
-groups = ["default", "code_quality", "docs", "interactive"]
-strategy = ["cross_platform", "inherit_metadata"]
-lock_version = "4.4.1"
-content_hash = "sha256:2c854f8da4b29c3080cd89c774409f95c47d3532c953cf10ecaa67d0b77ff9cf"
-
-[[package]]
-name = "appdirs"
-version = "1.4.4"
-summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-groups = ["default"]
-files = [
-    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
-    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
-]
-
-[[package]]
-name = "asttokens"
-version = "2.4.1"
-summary = "Annotate AST trees with source code positions"
-groups = ["interactive"]
-dependencies = [
-    "six>=1.12.0",
-]
-files = [
-    {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
-    {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
-]
-
-[[package]]
-name = "babel"
-version = "2.14.0"
-requires_python = ">=3.7"
-summary = "Internationalization utilities"
-groups = ["docs"]
-files = [
-    {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
-    {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
-]
-
-[[package]]
-name = "certifi"
-version = "2023.11.17"
-requires_python = ">=3.6"
-summary = "Python package for providing Mozilla's CA Bundle."
-groups = ["docs"]
-files = [
-    {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
-    {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
-]
-
-[[package]]
-name = "charset-normalizer"
-version = "3.3.2"
-requires_python = ">=3.7.0"
-summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-groups = ["docs"]
-files = [
-    {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
-    {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
-    {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
-    {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
-    {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
-]
-
-[[package]]
-name = "click"
-version = "8.1.7"
-requires_python = ">=3.7"
-summary = "Composable command line interface toolkit"
-groups = ["docs"]
-dependencies = [
-    "colorama; platform_system == \"Windows\"",
-]
-files = [
-    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
-    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
-]
-
-[[package]]
-name = "colorama"
-version = "0.4.6"
-requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
-summary = "Cross-platform colored terminal text."
-groups = ["docs", "interactive"]
-files = [
-    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
-    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
-]
-
-[[package]]
-name = "decorator"
-version = "5.1.1"
-requires_python = ">=3.5"
-summary = "Decorators for Humans"
-groups = ["interactive"]
-files = [
-    {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
-    {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
-]
-
-[[package]]
-name = "exceptiongroup"
-version = "1.2.0"
-requires_python = ">=3.7"
-summary = "Backport of PEP 654 (exception groups)"
-groups = ["interactive"]
-marker = "python_version < \"3.11\""
-files = [
-    {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
-    {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
-]
-
-[[package]]
-name = "executing"
-version = "2.0.1"
-requires_python = ">=3.5"
-summary = "Get the currently executing AST node of a frame, and other information"
-groups = ["interactive"]
-files = [
-    {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"},
-    {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"},
-]
-
-[[package]]
-name = "flake8"
-version = "7.0.0"
-requires_python = ">=3.8.1"
-summary = "the modular source code checker: pep8 pyflakes and co"
-groups = ["code_quality"]
-dependencies = [
-    "mccabe<0.8.0,>=0.7.0",
-    "pycodestyle<2.12.0,>=2.11.0",
-    "pyflakes<3.3.0,>=3.2.0",
-]
-files = [
-    {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"},
-    {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"},
-]
-
-[[package]]
-name = "ghp-import"
-version = "2.1.0"
-summary = "Copy your docs directly to the gh-pages branch."
-groups = ["docs"]
-dependencies = [
-    "python-dateutil>=2.8.1",
-]
-files = [
-    {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
-    {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
-]
-
-[[package]]
-name = "griffe"
-version = "0.38.1"
-requires_python = ">=3.8"
-summary = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
-groups = ["docs"]
-dependencies = [
-    "colorama>=0.4",
-]
-files = [
-    {file = "griffe-0.38.1-py3-none-any.whl", hash = "sha256:334c79d3b5964ade65c05dfcaf53518c576dedd387aaba5c9fd71212f34f1483"},
-    {file = "griffe-0.38.1.tar.gz", hash = "sha256:bd68d7da7f3d87bc57eb9962b250db123efd9bbcc06c11c1a91b6e583b2a9361"},
-]
-
-[[package]]
-name = "idna"
-version = "3.6"
-requires_python = ">=3.5"
-summary = "Internationalized Domain Names in Applications (IDNA)"
-groups = ["docs"]
-files = [
-    {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
-    {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
-]
-
-[[package]]
-name = "ipython"
-version = "8.19.0"
-requires_python = ">=3.10"
-summary = "IPython: Productive Interactive Computing"
-groups = ["interactive"]
-dependencies = [
-    "colorama; sys_platform == \"win32\"",
-    "decorator",
-    "exceptiongroup; python_version < \"3.11\"",
-    "jedi>=0.16",
-    "matplotlib-inline",
-    "pexpect>4.3; sys_platform != \"win32\"",
-    "prompt-toolkit<3.1.0,>=3.0.41",
-    "pygments>=2.4.0",
-    "stack-data",
-    "traitlets>=5",
-]
-files = [
-    {file = "ipython-8.19.0-py3-none-any.whl", hash = "sha256:2f55d59370f59d0d2b2212109fe0e6035cfea436b1c0e6150ad2244746272ec5"},
-    {file = "ipython-8.19.0.tar.gz", hash = "sha256:ac4da4ecf0042fb4e0ce57c60430c2db3c719fa8bdf92f8631d6bd8a5785d1f0"},
-]
-
-[[package]]
-name = "jedi"
-version = "0.19.1"
-requires_python = ">=3.6"
-summary = "An autocompletion tool for Python that can be used for text editors."
-groups = ["interactive"]
-dependencies = [
-    "parso<0.9.0,>=0.8.3",
-]
-files = [
-    {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"},
-    {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
-]
-
-[[package]]
-name = "jinja2"
-version = "3.1.2"
-requires_python = ">=3.7"
-summary = "A very fast and expressive template engine."
-groups = ["docs"]
-dependencies = [
-    "MarkupSafe>=2.0",
-]
-files = [
-    {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
-    {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
-]
-
-[[package]]
-name = "joblib"
-version = "1.3.2"
-requires_python = ">=3.7"
-summary = "Lightweight pipelining with Python functions"
-groups = ["default"]
-files = [
-    {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"},
-    {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"},
-]
-
-[[package]]
-name = "markdown"
-version = "3.5.1"
-requires_python = ">=3.8"
-summary = "Python implementation of John Gruber's Markdown."
-groups = ["docs"]
-files = [
-    {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"},
-    {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"},
-]
-
-[[package]]
-name = "markupsafe"
-version = "2.1.3"
-requires_python = ">=3.7"
-summary = "Safely add untrusted strings to HTML/XML markup."
-groups = ["docs"]
-files = [
-    {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
-    {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
-    {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
-    {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
-    {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
-]
-
-[[package]]
-name = "matplotlib-inline"
-version = "0.1.6"
-requires_python = ">=3.5"
-summary = "Inline Matplotlib backend for Jupyter"
-groups = ["interactive"]
-dependencies = [
-    "traitlets",
-]
-files = [
-    {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
-    {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
-]
-
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-requires_python = ">=3.6"
-summary = "McCabe checker, plugin for flake8"
-groups = ["code_quality"]
-files = [
-    {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
-    {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-
-[[package]]
-name = "mergedeep"
-version = "1.3.4"
-requires_python = ">=3.6"
-summary = "A deep merge function for 🐍."
-groups = ["docs"]
-files = [
-    {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
-    {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
-]
-
-[[package]]
-name = "mkdocs"
-version = "1.5.3"
-requires_python = ">=3.7"
-summary = "Project documentation with Markdown."
-groups = ["docs"]
-dependencies = [
-    "click>=7.0",
-    "colorama>=0.4; platform_system == \"Windows\"",
-    "ghp-import>=1.0",
-    "jinja2>=2.11.1",
-    "markdown>=3.2.1",
-    "markupsafe>=2.0.1",
-    "mergedeep>=1.3.4",
-    "packaging>=20.5",
-    "pathspec>=0.11.1",
-    "platformdirs>=2.2.0",
-    "pyyaml-env-tag>=0.1",
-    "pyyaml>=5.1",
-    "watchdog>=2.0",
-]
-files = [
-    {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"},
-    {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"},
-]
-
-[[package]]
-name = "mkdocs-autorefs"
-version = "0.5.0"
-requires_python = ">=3.8"
-summary = "Automatically link across pages in MkDocs."
-groups = ["docs"]
-dependencies = [
-    "Markdown>=3.3",
-    "mkdocs>=1.1",
-]
-files = [
-    {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"},
-    {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"},
-]
-
-[[package]]
-name = "mkdocs-material"
-version = "9.5.3"
-requires_python = ">=3.8"
-summary = "Documentation that simply works"
-groups = ["docs"]
-dependencies = [
-    "babel~=2.10",
-    "colorama~=0.4",
-    "jinja2~=3.0",
-    "markdown~=3.2",
-    "mkdocs-material-extensions~=1.3",
-    "mkdocs~=1.5.3",
-    "paginate~=0.5",
-    "pygments~=2.16",
-    "pymdown-extensions~=10.2",
-    "regex>=2022.4",
-    "requests~=2.26",
-]
-files = [
-    {file = "mkdocs_material-9.5.3-py3-none-any.whl", hash = "sha256:76c93a8525cceb0b395b9cedab3428bf518cf6439adef2b940f1c1574b775d89"},
-    {file = "mkdocs_material-9.5.3.tar.gz", hash = "sha256:5899219f422f0a6de784232d9d40374416302ffae3c160cacc72969fcc1ee372"},
-]
-
-[[package]]
-name = "mkdocs-material-extensions"
-version = "1.3.1"
-requires_python = ">=3.8"
-summary = "Extension pack for Python Markdown and MkDocs Material."
-groups = ["docs"]
-files = [
-    {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"},
-    {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"},
-]
-
-[[package]]
-name = "mkdocstrings"
-version = "0.24.0"
-requires_python = ">=3.8"
-summary = "Automatic documentation from sources, for MkDocs."
-groups = ["docs"]
-dependencies = [
-    "Jinja2>=2.11.1",
-    "Markdown>=3.3",
-    "MarkupSafe>=1.1",
-    "click>=7.0",
-    "mkdocs-autorefs>=0.3.1",
-    "mkdocs>=1.4",
-    "platformdirs>=2.2.0",
-    "pymdown-extensions>=6.3",
-]
-files = [
-    {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"},
-    {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"},
-]
-
-[[package]]
-name = "mkdocstrings-python"
-version = "1.7.5"
-requires_python = ">=3.8"
-summary = "A Python handler for mkdocstrings."
-groups = ["docs"]
-dependencies = [
-    "griffe>=0.37",
-    "mkdocstrings>=0.20",
-]
-files = [
-    {file = "mkdocstrings_python-1.7.5-py3-none-any.whl", hash = "sha256:5f6246026353f0c0785135db70c3fe9a5d9318990fc7ceb11d62097b8ffdd704"},
-    {file = "mkdocstrings_python-1.7.5.tar.gz", hash = "sha256:c7d143728257dbf1aa550446555a554b760dcd40a763f077189d298502b800be"},
-]
-
-[[package]]
-name = "mkdocstrings"
-version = "0.24.0"
-extras = ["python"]
-requires_python = ">=3.8"
-summary = "Automatic documentation from sources, for MkDocs."
-groups = ["docs"]
-dependencies = [
-    "mkdocstrings-python>=0.5.2",
-    "mkdocstrings==0.24.0",
-]
-files = [
-    {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"},
-    {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"},
-]
-
-[[package]]
-name = "mpmath"
-version = "1.3.0"
-summary = "Python library for arbitrary-precision floating-point arithmetic"
-groups = ["default"]
-files = [
-    {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"},
-    {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"},
-]
-
-[[package]]
-name = "mypy"
-version = "1.8.0"
-requires_python = ">=3.8"
-summary = "Optional static typing for Python"
-groups = ["code_quality"]
-dependencies = [
-    "mypy-extensions>=1.0.0",
-    "tomli>=1.1.0; python_version < \"3.11\"",
-    "typing-extensions>=4.1.0",
-]
-files = [
-    {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
-    {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
-    {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
-    {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
-    {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
-    {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
-    {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
-    {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
-    {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
-    {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
-    {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
-    {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
-    {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
-    {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
-    {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
-    {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
-    {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
-]
-
-[[package]]
-name = "mypy-extensions"
-version = "1.0.0"
-requires_python = ">=3.5"
-summary = "Type system extensions for programs checked with the mypy type checker."
-groups = ["code_quality"]
-files = [
-    {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
-    {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
-]
-
-[[package]]
-name = "numpy"
-version = "1.26.3"
-requires_python = ">=3.9"
-summary = "Fundamental package for array computing in Python"
-groups = ["default"]
-files = [
-    {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"},
-    {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"},
-    {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"},
-    {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"},
-    {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"},
-    {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"},
-    {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"},
-    {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"},
-    {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"},
-    {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"},
-    {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"},
-    {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"},
-    {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"},
-    {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"},
-    {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"},
-    {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"},
-    {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"},
-    {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"},
-    {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"},
-    {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"},
-    {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"},
-    {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"},
-    {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"},
-    {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"},
-    {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"},
-    {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"},
-    {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"},
-    {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"},
-]
-
-[[package]]
-name = "packaging"
-version = "23.2"
-requires_python = ">=3.7"
-summary = "Core utilities for Python packages"
-groups = ["docs"]
-files = [
-    {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
-    {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
-]
-
-[[package]]
-name = "paginate"
-version = "0.5.6"
-summary = "Divides large result sets into pages for easier browsing"
-groups = ["docs"]
-files = [
-    {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"},
-]
-
-[[package]]
-name = "parso"
-version = "0.8.3"
-requires_python = ">=3.6"
-summary = "A Python Parser"
-groups = ["interactive"]
-files = [
-    {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
-    {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
-]
-
-[[package]]
-name = "pathspec"
-version = "0.12.1"
-requires_python = ">=3.8"
-summary = "Utility library for gitignore style pattern matching of file paths."
-groups = ["docs"]
-files = [
-    {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
-    {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
-]
-
-[[package]]
-name = "pexpect"
-version = "4.9.0"
-summary = "Pexpect allows easy control of interactive console applications."
-groups = ["interactive"]
-marker = "sys_platform != \"win32\""
-dependencies = [
-    "ptyprocess>=0.5",
-]
-files = [
-    {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
-    {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
-]
-
-[[package]]
-name = "platformdirs"
-version = "4.1.0"
-requires_python = ">=3.8"
-summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-groups = ["docs"]
-files = [
-    {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
-    {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
-]
-
-[[package]]
-name = "prompt-toolkit"
-version = "3.0.43"
-requires_python = ">=3.7.0"
-summary = "Library for building powerful interactive command lines in Python"
-groups = ["interactive"]
-dependencies = [
-    "wcwidth",
-]
-files = [
-    {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"},
-    {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"},
-]
-
-[[package]]
-name = "ptyprocess"
-version = "0.7.0"
-summary = "Run a subprocess in a pseudo terminal"
-groups = ["interactive"]
-marker = "sys_platform != \"win32\""
-files = [
-    {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
-    {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
-]
-
-[[package]]
-name = "pure-eval"
-version = "0.2.2"
-summary = "Safely evaluate AST nodes without side effects"
-groups = ["interactive"]
-files = [
-    {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
-    {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
-]
-
-[[package]]
-name = "pycodestyle"
-version = "2.11.1"
-requires_python = ">=3.8"
-summary = "Python style guide checker"
-groups = ["code_quality"]
-files = [
-    {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"},
-    {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"},
-]
-
-[[package]]
-name = "pyflakes"
-version = "3.2.0"
-requires_python = ">=3.8"
-summary = "passive checker of Python programs"
-groups = ["code_quality"]
-files = [
-    {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
-    {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
-]
-
-[[package]]
-name = "pygments"
-version = "2.17.2"
-requires_python = ">=3.7"
-summary = "Pygments is a syntax highlighting package written in Python."
-groups = ["docs", "interactive"]
-files = [
-    {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
-    {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
-]
-
-[[package]]
-name = "pymdown-extensions"
-version = "10.7"
-requires_python = ">=3.8"
-summary = "Extension pack for Python Markdown."
-groups = ["docs"]
-dependencies = [
-    "markdown>=3.5",
-    "pyyaml",
-]
-files = [
-    {file = "pymdown_extensions-10.7-py3-none-any.whl", hash = "sha256:6ca215bc57bc12bf32b414887a68b810637d039124ed9b2e5bd3325cbb2c050c"},
-    {file = "pymdown_extensions-10.7.tar.gz", hash = "sha256:c0d64d5cf62566f59e6b2b690a4095c931107c250a8c8e1351c1de5f6b036deb"},
-]
-
-[[package]]
-name = "pystencils"
-version = "1.3.3"
-requires_python = ">=3.8"
-summary = "Speeding up stencil computations on CPUs and GPUs"
-groups = ["default"]
-dependencies = [
-    "appdirs",
-    "joblib",
-    "numpy>=1.8.0",
-    "sympy<=1.11.1,>=1.6",
-]
-files = [
-    {file = "pystencils-1.3.3.tar.gz", hash = "sha256:d066d8dd6eee355671dd6e93454f9a1bf143ab2528f76076ce15f0c15b5d29c7"},
-]
-
-[[package]]
-name = "python-dateutil"
-version = "2.8.2"
-requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
-summary = "Extensions to the standard Python datetime module"
-groups = ["docs"]
-dependencies = [
-    "six>=1.5",
-]
-files = [
-    {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
-    {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
-]
-
-[[package]]
-name = "pyyaml"
-version = "6.0.1"
-requires_python = ">=3.6"
-summary = "YAML parser and emitter for Python"
-groups = ["docs"]
-files = [
-    {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
-    {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
-    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
-    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
-    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
-    {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
-    {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
-    {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
-    {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
-    {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
-    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
-    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
-    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
-    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
-    {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
-    {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
-    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
-    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
-    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
-    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
-    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
-    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
-    {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
-]
-
-[[package]]
-name = "pyyaml-env-tag"
-version = "0.1"
-requires_python = ">=3.6"
-summary = "A custom YAML tag for referencing environment variables in YAML files. "
-groups = ["docs"]
-dependencies = [
-    "pyyaml",
-]
-files = [
-    {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
-    {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
-]
-
-[[package]]
-name = "regex"
-version = "2023.12.25"
-requires_python = ">=3.7"
-summary = "Alternative regular expression module, to replace re."
-groups = ["docs"]
-files = [
-    {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
-    {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
-    {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
-    {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
-    {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
-    {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
-    {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
-    {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
-    {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
-    {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
-    {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
-    {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
-    {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
-    {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
-    {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
-    {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
-    {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
-    {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
-    {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
-    {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
-    {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
-    {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
-    {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
-    {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
-    {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
-    {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
-    {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
-    {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
-    {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
-    {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
-    {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
-    {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
-    {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
-    {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
-    {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
-    {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
-    {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
-    {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
-    {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
-    {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
-    {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
-    {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
-]
-
-[[package]]
-name = "requests"
-version = "2.31.0"
-requires_python = ">=3.7"
-summary = "Python HTTP for Humans."
-groups = ["docs"]
-dependencies = [
-    "certifi>=2017.4.17",
-    "charset-normalizer<4,>=2",
-    "idna<4,>=2.5",
-    "urllib3<3,>=1.21.1",
-]
-files = [
-    {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
-    {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
-]
-
-[[package]]
-name = "six"
-version = "1.16.0"
-requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-summary = "Python 2 and 3 compatibility utilities"
-groups = ["docs", "interactive"]
-files = [
-    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
-    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
-
-[[package]]
-name = "stack-data"
-version = "0.6.3"
-summary = "Extract data from python stack frames and tracebacks for informative displays"
-groups = ["interactive"]
-dependencies = [
-    "asttokens>=2.1.0",
-    "executing>=1.2.0",
-    "pure-eval",
-]
-files = [
-    {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
-    {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
-]
-
-[[package]]
-name = "sympy"
-version = "1.11.1"
-requires_python = ">=3.8"
-summary = "Computer algebra system (CAS) in Python"
-groups = ["default"]
-dependencies = [
-    "mpmath>=0.19",
-]
-files = [
-    {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"},
-    {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"},
-]
-
-[[package]]
-name = "tomli"
-version = "2.0.1"
-requires_python = ">=3.7"
-summary = "A lil' TOML parser"
-groups = ["code_quality"]
-marker = "python_version < \"3.11\""
-files = [
-    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
-    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
-]
-
-[[package]]
-name = "traitlets"
-version = "5.14.1"
-requires_python = ">=3.8"
-summary = "Traitlets Python configuration system"
-groups = ["interactive"]
-files = [
-    {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"},
-    {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"},
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.9.0"
-requires_python = ">=3.8"
-summary = "Backported and Experimental Type Hints for Python 3.8+"
-groups = ["code_quality"]
-files = [
-    {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
-    {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
-]
-
-[[package]]
-name = "urllib3"
-version = "2.1.0"
-requires_python = ">=3.8"
-summary = "HTTP library with thread-safe connection pooling, file post, and more."
-groups = ["docs"]
-files = [
-    {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"},
-    {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"},
-]
-
-[[package]]
-name = "watchdog"
-version = "3.0.0"
-requires_python = ">=3.7"
-summary = "Filesystem events monitoring"
-groups = ["docs"]
-files = [
-    {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"},
-    {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"},
-    {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"},
-    {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"},
-    {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"},
-    {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"},
-    {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"},
-    {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"},
-    {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"},
-    {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"},
-    {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"},
-    {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"},
-    {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"},
-    {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"},
-]
-
-[[package]]
-name = "wcwidth"
-version = "0.2.13"
-summary = "Measures the displayed width of unicode strings in a terminal"
-groups = ["interactive"]
-files = [
-    {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
-    {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
-]
diff --git a/pyproject.toml b/pyproject.toml
index 8a0029dc1e2bfd0e98ce23732a87e228022f0439..5b4e66b32136796c2f1b2cc2494b4a2054cedad8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,11 +5,11 @@ authors = [
     {name = "Frederik Hennig", email = "frederik.hennig@fau.de"},
 ]
 dependencies = [
-    "pystencils>=1.3.2",
+    "pystencils>=2.0.dev0",
 ]
 requires-python = ">=3.10"
 readme = "README.md"
-license = {text = "noneyet"}
+license = { file = "COPYING.txt" }
 dynamic = ["version"]
 
 [project.scripts]
@@ -18,23 +18,25 @@ sfg-cli = "pystencilssfg.cli:cli_main"
 [build-system]
 requires = [
     "setuptools>=69",
-    "versioneer>=0.29",
-    "tomli; python_version < '3.11'"
+    "versioneer[toml]>=0.29",
 ]
 build-backend = "setuptools.build_meta"
 
-[tool.pdm.dev-dependencies]
+[project.optional-dependencies]
 interactive = [
     "ipython>=8.17.2",
 ]
-code_quality = [
+testing = [
     "flake8>=6.1.0",
     "mypy>=1.7.0",
 ]
 docs = [
-    "mkdocs>=1.5.3",
-    "mkdocs-material>=9.4.8",
-    "mkdocstrings[python]>=0.24.0",
+    "sphinx",
+    "furo",
+    "myst-parser",
+    "sphinx_design",
+    "sphinx_autodoc_typehints",
+    "sphinx-copybutton"
 ]
 
 [tool.versioneer]
@@ -42,5 +44,5 @@ VCS = "git"
 style = "pep440"
 versionfile_source = "src/pystencilssfg/_version.py"
 versionfile_build = "pystencilssfg/_version.py"
-tag_prefix = ""
+tag_prefix = "v"
 parentdir_prefix = "pystencilssfg-"
diff --git a/src/pystencilssfg/__init__.py b/src/pystencilssfg/__init__.py
index 457c89ccde0ff32341d6555abb991ddf660e56f6..247800ca5c095a808b9e0f03ccab66e40a134035 100644
--- a/src/pystencilssfg/__init__.py
+++ b/src/pystencilssfg/__init__.py
@@ -1,10 +1,17 @@
-from .configuration import SfgConfiguration
+from .configuration import SfgConfiguration, SfgOutputMode
 from .generator import SourceFileGenerator
 from .composer import SfgComposer
 from .context import SfgContext
+from .lang import AugExpr
 
-__all__ = ["SourceFileGenerator", "SfgComposer", "SfgConfiguration", "SfgContext"]
+__all__ = [
+    "SourceFileGenerator",
+    "SfgComposer",
+    "SfgConfiguration",
+    "SfgOutputMode",
+    "SfgContext",
+    "AugExpr",
+]
 
 from . import _version
-
-__version__ = _version.get_versions()["version"]
+__version__ = _version.get_versions()['version']
diff --git a/src/pystencilssfg/_version.py b/src/pystencilssfg/_version.py
index c9dee16820d7d0e6957171a9472d2b8e6c513b05..3ac6be9aa8bbf0b129bd1165fa7ac8d68ba69a6d 100644
--- a/src/pystencilssfg/_version.py
+++ b/src/pystencilssfg/_version.py
@@ -1,3 +1,4 @@
+
 # This file helps to compute a version number in source trees obtained from
 # git-archive tarball (such as those provided by githubs download-from-tag
 # feature). Distribution tarballs (built by setup.py sdist) and build
@@ -50,7 +51,7 @@ def get_config() -> VersioneerConfig:
     cfg = VersioneerConfig()
     cfg.VCS = "git"
     cfg.style = "pep440"
-    cfg.tag_prefix = ""
+    cfg.tag_prefix = "v"
     cfg.parentdir_prefix = "pystencilssfg-"
     cfg.versionfile_source = "src/pystencilssfg/_version.py"
     cfg.verbose = False
@@ -67,14 +68,12 @@ HANDLERS: Dict[str, Dict[str, Callable]] = {}
 
 def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
     """Create decorator to mark a method as the handler of a VCS."""
-
     def decorate(f: Callable) -> Callable:
         """Store f in HANDLERS[vcs][method]."""
         if vcs not in HANDLERS:
             HANDLERS[vcs] = {}
         HANDLERS[vcs][method] = f
         return f
-
     return decorate
 
 
@@ -101,14 +100,10 @@ def run_command(
         try:
             dispcmd = str([command] + args)
             # remember shell=False, so use git.cmd on windows, not just git
-            process = subprocess.Popen(
-                [command] + args,
-                cwd=cwd,
-                env=env,
-                stdout=subprocess.PIPE,
-                stderr=(subprocess.PIPE if hide_stderr else None),
-                **popen_kwargs,
-            )
+            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
+                                       stdout=subprocess.PIPE,
+                                       stderr=(subprocess.PIPE if hide_stderr
+                                               else None), **popen_kwargs)
             break
         except OSError as e:
             if e.errno == errno.ENOENT:
@@ -146,21 +141,15 @@ def versions_from_parentdir(
     for _ in range(3):
         dirname = os.path.basename(root)
         if dirname.startswith(parentdir_prefix):
-            return {
-                "version": dirname[len(parentdir_prefix) :],
-                "full-revisionid": None,
-                "dirty": False,
-                "error": None,
-                "date": None,
-            }
+            return {"version": dirname[len(parentdir_prefix):],
+                    "full-revisionid": None,
+                    "dirty": False, "error": None, "date": None}
         rootdirs.append(root)
         root = os.path.dirname(root)  # up a level
 
     if verbose:
-        print(
-            "Tried directories %s but none started with prefix %s"
-            % (str(rootdirs), parentdir_prefix)
-        )
+        print("Tried directories %s but none started with prefix %s" %
+              (str(rootdirs), parentdir_prefix))
     raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
 
 
@@ -223,7 +212,7 @@ def git_versions_from_keywords(
     # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
     # just "foo-1.0". If we see a "tag: " prefix, prefer those.
     TAG = "tag: "
-    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
     if not tags:
         # Either we're using git < 1.8.3, or there really are no tags. We use
         # a heuristic: assume all version tags have a digit. The old git %d
@@ -232,7 +221,7 @@ def git_versions_from_keywords(
         # between branches and tags. By ignoring refnames without digits, we
         # filter out many common branch names like "release" and
         # "stabilization", as well as "HEAD" and "master".
-        tags = {r for r in refs if re.search(r"\d", r)}
+        tags = {r for r in refs if re.search(r'\d', r)}
         if verbose:
             print("discarding '%s', no digits" % ",".join(refs - tags))
     if verbose:
@@ -240,36 +229,32 @@ def git_versions_from_keywords(
     for ref in sorted(tags):
         # sorting will prefer e.g. "2.0" over "2.0rc1"
         if ref.startswith(tag_prefix):
-            r = ref[len(tag_prefix) :]
+            r = ref[len(tag_prefix):]
             # Filter out refs that exactly match prefix or that don't start
             # with a number once the prefix is stripped (mostly a concern
             # when prefix is '')
-            if not re.match(r"\d", r):
+            if not re.match(r'\d', r):
                 continue
             if verbose:
                 print("picking %s" % r)
-            return {
-                "version": r,
-                "full-revisionid": keywords["full"].strip(),
-                "dirty": False,
-                "error": None,
-                "date": date,
-            }
+            return {"version": r,
+                    "full-revisionid": keywords["full"].strip(),
+                    "dirty": False, "error": None,
+                    "date": date}
     # no suitable tags, so version is "0+unknown", but full hex is still there
     if verbose:
         print("no suitable tags, using unknown + full revision id")
-    return {
-        "version": "0+unknown",
-        "full-revisionid": keywords["full"].strip(),
-        "dirty": False,
-        "error": "no suitable tags",
-        "date": None,
-    }
+    return {"version": "0+unknown",
+            "full-revisionid": keywords["full"].strip(),
+            "dirty": False, "error": "no suitable tags", "date": None}
 
 
 @register_vcs_handler("git", "pieces_from_vcs")
 def git_pieces_from_vcs(
-    tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command
+    tag_prefix: str,
+    root: str,
+    verbose: bool,
+    runner: Callable = run_command
 ) -> Dict[str, Any]:
     """Get version from 'git describe' in the root of the source tree.
 
@@ -288,7 +273,8 @@ def git_pieces_from_vcs(
     env.pop("GIT_DIR", None)
     runner = functools.partial(runner, env=env)
 
-    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
+                   hide_stderr=not verbose)
     if rc != 0:
         if verbose:
             print("Directory %s not under git control" % root)
@@ -296,19 +282,10 @@ def git_pieces_from_vcs(
 
     # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
     # if there isn't one, this yields HEX[-dirty] (no NUM)
-    describe_out, rc = runner(
-        GITS,
-        [
-            "describe",
-            "--tags",
-            "--dirty",
-            "--always",
-            "--long",
-            "--match",
-            f"{tag_prefix}[[:digit:]]*",
-        ],
-        cwd=root,
-    )
+    describe_out, rc = runner(GITS, [
+        "describe", "--tags", "--dirty", "--always", "--long",
+        "--match", f"{tag_prefix}[[:digit:]]*"
+    ], cwd=root)
     # --long was added in git-1.5.5
     if describe_out is None:
         raise NotThisMethod("'git describe' failed")
@@ -323,7 +300,8 @@ def git_pieces_from_vcs(
     pieces["short"] = full_out[:7]  # maybe improved later
     pieces["error"] = None
 
-    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
+    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
+                             cwd=root)
     # --abbrev-ref was added in git-1.6.3
     if rc != 0 or branch_name is None:
         raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
@@ -363,16 +341,17 @@ def git_pieces_from_vcs(
     dirty = git_describe.endswith("-dirty")
     pieces["dirty"] = dirty
     if dirty:
-        git_describe = git_describe[: git_describe.rindex("-dirty")]
+        git_describe = git_describe[:git_describe.rindex("-dirty")]
 
     # now we have TAG-NUM-gHEX or HEX
 
     if "-" in git_describe:
         # TAG-NUM-gHEX
-        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
         if not mo:
             # unparsable. Maybe git-describe is misbehaving?
-            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+            pieces["error"] = ("unable to parse git-describe output: '%s'"
+                               % describe_out)
             return pieces
 
         # tag
@@ -381,12 +360,10 @@ def git_pieces_from_vcs(
             if verbose:
                 fmt = "tag '%s' doesn't start with prefix '%s'"
                 print(fmt % (full_tag, tag_prefix))
-            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
-                full_tag,
-                tag_prefix,
-            )
+            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
+                               % (full_tag, tag_prefix))
             return pieces
-        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+        pieces["closest-tag"] = full_tag[len(tag_prefix):]
 
         # distance: number of commits since tag
         pieces["distance"] = int(mo.group(2))
@@ -435,7 +412,8 @@ def render_pep440(pieces: Dict[str, Any]) -> str:
                 rendered += ".dirty"
     else:
         # exception #1
-        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
+                                          pieces["short"])
         if pieces["dirty"]:
             rendered += ".dirty"
     return rendered
@@ -464,7 +442,8 @@ def render_pep440_branch(pieces: Dict[str, Any]) -> str:
         rendered = "0"
         if pieces["branch"] != "master":
             rendered += ".dev0"
-        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+        rendered += "+untagged.%d.g%s" % (pieces["distance"],
+                                          pieces["short"])
         if pieces["dirty"]:
             rendered += ".dirty"
     return rendered
@@ -625,13 +604,11 @@ def render_git_describe_long(pieces: Dict[str, Any]) -> str:
 def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
     """Render the given version pieces into the requested style."""
     if pieces["error"]:
-        return {
-            "version": "unknown",
-            "full-revisionid": pieces.get("long"),
-            "dirty": None,
-            "error": pieces["error"],
-            "date": None,
-        }
+        return {"version": "unknown",
+                "full-revisionid": pieces.get("long"),
+                "dirty": None,
+                "error": pieces["error"],
+                "date": None}
 
     if not style or style == "default":
         style = "pep440"  # the default
@@ -655,13 +632,9 @@ def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
     else:
         raise ValueError("unknown style '%s'" % style)
 
-    return {
-        "version": rendered,
-        "full-revisionid": pieces["long"],
-        "dirty": pieces["dirty"],
-        "error": None,
-        "date": pieces.get("date"),
-    }
+    return {"version": rendered, "full-revisionid": pieces["long"],
+            "dirty": pieces["dirty"], "error": None,
+            "date": pieces.get("date")}
 
 
 def get_versions() -> Dict[str, Any]:
@@ -675,7 +648,8 @@ def get_versions() -> Dict[str, Any]:
     verbose = cfg.verbose
 
     try:
-        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
+        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
+                                          verbose)
     except NotThisMethod:
         pass
 
@@ -684,16 +658,13 @@ def get_versions() -> Dict[str, Any]:
         # versionfile_source is the relative path from the top of the source
         # tree (where the .git directory might live) to this file. Invert
         # this to find the root from __file__.
-        for _ in cfg.versionfile_source.split("/"):
+        for _ in cfg.versionfile_source.split('/'):
             root = os.path.dirname(root)
     except NameError:
-        return {
-            "version": "0+unknown",
-            "full-revisionid": None,
-            "dirty": None,
-            "error": "unable to find root of source tree",
-            "date": None,
-        }
+        return {"version": "0+unknown", "full-revisionid": None,
+                "dirty": None,
+                "error": "unable to find root of source tree",
+                "date": None}
 
     try:
         pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
@@ -707,10 +678,6 @@ def get_versions() -> Dict[str, Any]:
     except NotThisMethod:
         pass
 
-    return {
-        "version": "0+unknown",
-        "full-revisionid": None,
-        "dirty": None,
-        "error": "unable to compute version",
-        "date": None,
-    }
+    return {"version": "0+unknown", "full-revisionid": None,
+            "dirty": None,
+            "error": "unable to compute version", "date": None}
diff --git a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
index 9d477cd23c52200da42fe8d349679080e5cc6f16..a29d70e7a49f74174c4f6e5a45a0da711e2a1e94 100644
--- a/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
+++ b/src/pystencilssfg/cmake/modules/PystencilsSfg.cmake
@@ -43,14 +43,15 @@ endfunction()
 
 
 function(pystencilssfg_generate_target_sources TARGET)
-    set(options HEADER_ONLY)
+    set(options)
+    set(oneValueArgs OUTPUT_MODE)
     set(multiValueArgs SCRIPTS DEPENDS FILE_EXTENSIONS)
     cmake_parse_arguments(_pssfg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
     set(generatorArgs)
 
-    if(_pssfg_HEADER_ONLY)
-        list(APPEND generatorArgs "--sfg-header-only")
+    if(DEFINED _pssfg_OUTPUT_MODE)
+        list(APPEND generatorArgs "--sfg-output-mode=${_pssfg_OUTPUT_MODE}")
     endif()
 
     if(DEFINED PystencilsSfg_CONFIGURATOR_SCRIPT)
diff --git a/src/pystencilssfg/composer/__init__.py b/src/pystencilssfg/composer/__init__.py
index c20f7def3ca09aedcbd043f8be71abb82e435d9d..f6af76b8b9c36445990fc451983fec5c14a4cf34 100644
--- a/src/pystencilssfg/composer/__init__.py
+++ b/src/pystencilssfg/composer/__init__.py
@@ -1,5 +1,23 @@
 from .composer import SfgComposer
-from .basic_composer import SfgBasicComposer, make_sequence
+from .basic_composer import (
+    SfgIComposer,
+    SfgBasicComposer,
+    make_sequence,
+    make_statements,
+    SequencerArg,
+    ExprLike,
+)
+from .mixin import SfgComposerMixIn
 from .class_composer import SfgClassComposer
 
-__all__ = ["SfgComposer", "make_sequence", "SfgBasicComposer", "SfgClassComposer"]
+__all__ = [
+    "SfgIComposer",
+    "SfgComposer",
+    "SfgComposerMixIn",
+    "make_sequence",
+    "make_statements",
+    "SequencerArg",
+    "ExprLike",
+    "SfgBasicComposer",
+    "SfgClassComposer",
+]
diff --git a/src/pystencilssfg/composer/basic_composer.py b/src/pystencilssfg/composer/basic_composer.py
index 1421aac2f07efacc0ac831f6851b9b25aacdf655..59938026ed670cb0e22c678220e7254312146eef 100644
--- a/src/pystencilssfg/composer/basic_composer.py
+++ b/src/pystencilssfg/composer/basic_composer.py
@@ -1,24 +1,34 @@
 from __future__ import annotations
-from typing import TYPE_CHECKING, Sequence
+from typing import Sequence
 from abc import ABC, abstractmethod
 import numpy as np
+import sympy as sp
+from functools import reduce
 
-from pystencils import Field, TypedSymbol
-from pystencils.astnodes import KernelFunction
+from pystencils import Field
+from pystencils.backend import KernelParameter, KernelFunction
+from pystencils.types import create_type, UserTypeSpec, PsCustomType, PsPointerType
 
+from ..context import SfgContext
 from .custom import CustomGenerator
-from ..tree import (
+from ..ir import (
     SfgCallTreeNode,
     SfgKernelCallNode,
+    SfgCudaKernelInvocation,
     SfgStatements,
     SfgFunctionParams,
     SfgRequireIncludes,
     SfgSequence,
     SfgBlock,
+    SfgBranch,
+    SfgSwitch,
 )
-from ..tree.deferred_nodes import SfgDeferredFieldMapping
-from ..tree.conditional import SfgCondition, SfgCustomCondition, SfgBranch, SfgSwitch
-from ..source_components import (
+from ..ir.postprocessing import (
+    SfgDeferredParamMapping,
+    SfgDeferredFieldMapping,
+    SfgDeferredVectorMapping,
+)
+from ..ir.source_components import (
     SfgFunction,
     SfgHeaderInclude,
     SfgKernelNamespace,
@@ -27,25 +37,38 @@ from ..source_components import (
     SfgConstructor,
     SfgMemberVariable,
     SfgClassKeyword,
+    SfgVar,
 )
-from ..source_concepts import SrcObject, SrcField, TypedSymbolOrObject, SrcVector
-from ..types import cpp_typename, SrcType
+from ..lang import IFieldExtraction, SrcVector, AugExpr, SrcField
 from ..exceptions import SfgException
 
-if TYPE_CHECKING:
-    from ..context import SfgContext
-
-
-class SfgBasicComposer:
-    """Composer for basic source components."""
 
+class SfgIComposer(ABC):
     def __init__(self, ctx: SfgContext):
-        self._ctx: SfgContext = ctx
+        self._ctx = ctx
 
     @property
     def context(self):
         return self._ctx
 
+
+class SfgNodeBuilder(ABC):
+    @abstractmethod
+    def resolve(self) -> SfgCallTreeNode:
+        pass
+
+
+ExprLike = str | SfgVar | AugExpr
+SequencerArg = tuple | str | AugExpr | SfgCallTreeNode | SfgNodeBuilder
+
+
+class SfgBasicComposer(SfgIComposer):
+    """Composer for basic source components, and base class for all composer mix-ins."""
+
+    def __init__(self, sfg: SfgContext | SfgIComposer):
+        ctx: SfgContext = sfg if isinstance(sfg, SfgContext) else sfg.context
+        super().__init__(ctx)
+
     def prelude(self, content: str):
         """Append a string to the prelude comment, to be printed at the top of both generated files.
 
@@ -75,11 +98,13 @@ class SfgBasicComposer:
 
     @property
     def kernels(self) -> SfgKernelNamespace:
-        """The default kernel namespace. Add kernels like:
-        ```Python
-        sfg.kernels.add(ast, "kernel_name")
-        sfg.kernels.create(assignments, "kernel_name", config)
-        ```"""
+        """The default kernel namespace.
+
+        Add kernels like::
+
+            sfg.kernels.add(ast, "kernel_name")
+            sfg.kernels.create(assignments, "kernel_name", config)
+        """
         return self._ctx._default_kernel_namespace
 
     def kernel_namespace(self, name: str) -> SfgKernelNamespace:
@@ -99,7 +124,7 @@ class SfgBasicComposer:
             private: If `True`, in header-implementation code generation, the header file is
                 only included in the implementation file.
         """
-        self._ctx.add_include(parse_include(header_file, private))
+        self._ctx.add_include(SfgHeaderInclude.parse(header_file, private))
 
     def numpy_struct(
         self, name: str, dtype: np.dtype, add_constructor: bool = True
@@ -143,14 +168,13 @@ class SfgBasicComposer:
 
         The syntax of this function adder uses a chain of two calls to mimic C++ syntax:
 
-        ```Python
-        sfg.function("FunctionName")(
-            # Function Body
-        )
-        ```
+        .. code-block:: Python
+
+            sfg.function("FunctionName")(
+                # Function Body
+            )
 
-        The function body is constructed via sequencing;
-        refer to [make_sequence][pystencilssfg.composer.make_sequence].
+        The function body is constructed via sequencing (see `make_sequence`).
         """
         if self._ctx.get_function(name) is not None:
             raise ValueError(f"Function {name} already exists.")
@@ -162,137 +186,205 @@ class SfgBasicComposer:
 
         return sequencer
 
-    def call(self, kernel_handle: SfgKernelHandle) -> SfgKernelCallNode:
-        """Use inside a function body to generate a kernel call.
+    def call(self, kernel_handle: SfgKernelHandle) -> SfgCallTreeNode:
+        """Use inside a function body to directly call a kernel.
+
+        When using `call`, the given kernel will simply be called as a function.
+        To invoke a GPU kernel on a specified launch grid, use `cuda_invoke`
+        or the interfaces of `pystencilssfg.extensions.sycl` instead.
 
         Args:
             kernel_handle: Handle to a kernel previously added to some kernel namespace.
         """
         return SfgKernelCallNode(kernel_handle)
 
+    def cuda_invoke(
+        self,
+        kernel_handle: SfgKernelHandle,
+        num_blocks: ExprLike,
+        threads_per_block: ExprLike,
+        stream: ExprLike | None,
+    ):
+        num_blocks_str = str(num_blocks)
+        tpb_str = str(threads_per_block)
+        stream_str = str(stream) if stream is not None else None
+        depends = _depends(num_blocks) | _depends(threads_per_block) | _depends(stream)
+        return SfgCudaKernelInvocation(
+            kernel_handle, num_blocks_str, tpb_str, stream_str, depends
+        )
+
     def seq(self, *args: tuple | str | SfgCallTreeNode | SfgNodeBuilder) -> SfgSequence:
-        """Syntax sequencing. For details, refer to [make_sequence][pystencilssfg.composer.make_sequence]"""
+        """Syntax sequencing. For details, see `make_sequence`"""
         return make_sequence(*args)
 
-    def params(self, *args: TypedSymbolOrObject) -> SfgFunctionParams:
+    def params(self, *args: SfgVar) -> SfgFunctionParams:
         """Use inside a function body to add parameters to the function."""
         return SfgFunctionParams(args)
 
     def require(self, *includes: str | SfgHeaderInclude) -> SfgRequireIncludes:
-        return SfgRequireIncludes(list(parse_include(incl) for incl in includes))
+        return SfgRequireIncludes(
+            list(SfgHeaderInclude.parse(incl) for incl in includes)
+        )
+
+    def cpptype(
+        self,
+        typename: UserTypeSpec,
+        ptr: bool = False,
+        ref: bool = False,
+        const: bool = False,
+    ):
+        if ptr and ref:
+            raise SfgException("Create either a pointer, or a ref type, not both!")
+
+        ref_qual = "&" if ref else ""
+        try:
+            base_type = create_type(typename)
+        except ValueError:
+            if not isinstance(typename, str):
+                raise ValueError(f"Could not parse type: {typename}")
+
+            base_type = PsCustomType(typename + ref_qual, const=const)
+
+        if ptr:
+            return PsPointerType(base_type)
+        else:
+            return base_type
+
+    def var(self, name: str, dtype: UserTypeSpec) -> SfgVar:
+        """Create a variable with given name and data type."""
+        return SfgVar(name, create_type(dtype))
+
+    def init(self, lhs: SfgVar) -> SfgInplaceInitBuilder:
+        """Create a C++ in-place initialization.
+
+        Usage:
+
+        .. code-block:: Python
+
+            obj = sfg.var("obj", "SomeClass")
+            sfg.init(obj)(arg1, arg2, arg3)
+
+        becomes
+
+        .. code-block:: C++
+
+            SomeClass obj { arg1, arg2, arg3 };
+        """
+        return SfgInplaceInitBuilder(lhs)
+
+    def expr(self, fmt: str, *deps, **kwdeps):
+        return AugExpr.format(fmt, *deps, **kwdeps)
 
     @property
     def branch(self) -> SfgBranchBuilder:
         """Use inside a function body to create an if/else conditonal branch.
 
         The syntax is:
-        ```Python
-        sfg.branch("condition")(
-            # then-body
-        )(
-            # else-body (may be omitted)
-        )
-        ```
+
+        .. code-block:: Python
+
+            sfg.branch("condition")(
+                # then-body
+            )(
+                # else-body (may be omitted)
+            )
         """
         return SfgBranchBuilder()
 
-    def switch(self, switch_arg: str | TypedSymbolOrObject) -> SfgSwitchBuilder:
+    def switch(self, switch_arg: ExprLike) -> SfgSwitchBuilder:
         return SfgSwitchBuilder(switch_arg)
 
-    def map_field(self, field: Field, src_object: SrcField) -> SfgDeferredFieldMapping:
+    def map_field(
+        self, field: Field, index_provider: IFieldExtraction | SrcField
+    ) -> SfgDeferredFieldMapping:
         """Map a pystencils field to a field data structure, from which pointers, sizes
         and strides should be extracted.
 
         Args:
             field: The pystencils field to be mapped
-            src_object: A [SrcField][pystencilssfg.source_concepts.SrcField] object representing a field data structure.
+            src_object: A `IFieldIndexingProvider` object representing a field data structure.
         """
-        return SfgDeferredFieldMapping(field, src_object)
+        return SfgDeferredFieldMapping(field, index_provider)
 
     def map_param(
         self,
-        lhs: TypedSymbolOrObject,
-        rhs: TypedSymbolOrObject | Sequence[TypedSymbolOrObject],
+        lhs: SfgVar,
+        rhs: SfgVar | Sequence[SfgVar],
         mapping: str,
     ):
         """Arbitrary parameter mapping: Add a single line of code to define a left-hand
         side object from one or multiple right-hand side dependencies."""
-        if isinstance(rhs, (TypedSymbol, SrcObject)):
-            return SfgStatements(mapping, (lhs,), (rhs,))
-        else:
-            return SfgStatements(mapping, (lhs,), rhs)
+        if isinstance(rhs, (KernelParameter, SfgVar)):
+            rhs = [rhs]
+        return SfgDeferredParamMapping(lhs, set(rhs), mapping)
 
-    def map_vector(self, lhs_components: Sequence[TypedSymbolOrObject], rhs: SrcVector):
+    def map_vector(self, lhs_components: Sequence[SfgVar | sp.Symbol], rhs: SrcVector):
         """Extracts scalar numerical values from a vector data type.
 
         Args:
             lhs_components: Vector components as a list of symbols.
-            rhs: A [SrcVector][pystencilssfg.source_concepts.SrcVector] object representing a vector data structure.
+            rhs: A `SrcVector` object representing a vector data structure.
         """
-        return make_sequence(
-            *(
-                rhs.extract_component(dest, coord)
-                for coord, dest in enumerate(lhs_components)
-            )
-        )
-
-
-class SfgNodeBuilder(ABC):
-    @abstractmethod
-    def resolve(self) -> SfgCallTreeNode:
-        pass
+        return SfgDeferredVectorMapping(lhs_components, rhs)
 
 
-SequencerArg = tuple | str | SfgCallTreeNode | SfgNodeBuilder
+def make_statements(arg: ExprLike) -> SfgStatements:
+    match arg:
+        case str():
+            return SfgStatements(arg, (), ())
+        case SfgVar(name, _):
+            return SfgStatements(name, (), (arg,))
+        case AugExpr():
+            return SfgStatements(str(arg), (), arg.depends)
+        case _:
+            assert False
 
 
 def make_sequence(*args: SequencerArg) -> SfgSequence:
     """Construct a sequence of C++ code from various kinds of arguments.
 
     `make_sequence` is ubiquitous throughout the function building front-end;
-    among others, it powers the syntax of
-    [SfgComposer.function][pystencilssfg.SfgComposer.function] and
-    [SfgComposer.branch][pystencilssfg.SfgComposer.branch].
+    among others, it powers the syntax of `SfgComposer.function` and `SfgComposer.branch`.
 
     `make_sequence` constructs an abstract syntax tree for code within a function body, accepting various
-    types of arguments which then get turned into C++ code. These are:
+    types of arguments which then get turned into C++ code. These are
 
-     - Strings (`str`) are printed as-is
-     - Tuples (`tuple`) signify *blocks*, i.e. C++ code regions enclosed in `{ }`
-     - Sub-ASTs and AST builders, which are often produced by the syntactic sugar and
-       factory methods of [SfgComposer][pystencilssfg.SfgComposer].
+    - Strings (`str`) are printed as-is
+    - Tuples (`tuple`) signify *blocks*, i.e. C++ code regions enclosed in ``{ }``
+    - Sub-ASTs and AST builders, which are often produced by the syntactic sugar and
+      factory methods of `SfgComposer`.
 
     Its usage is best shown by example:
 
-    ```Python
-    tree = make_sequence(
-        "int a = 0;",
-        "int b = 1;",
-        (
-            "int tmp = b;",
-            "b = a;",
-            "a = tmp;"
-        ),
-        SfgKernelCall(kernel_handle)
-    )
-
-    sfg.context.add_function("myFunction", tree)
-    ```
+    .. code-block:: Python
+
+        tree = make_sequence(
+            "int a = 0;",
+            "int b = 1;",
+            (
+                "int tmp = b;",
+                "b = a;",
+                "a = tmp;"
+            ),
+            SfgKernelCall(kernel_handle)
+        )
+
+        sfg.context.add_function("myFunction", tree)
 
     will translate to
 
-    ```C++
-    void myFunction() {
-        int a = 0;
-        int b = 0;
-        {
-            int tmp = b;
-            b = a;
-            a = tmp;
+    .. code-block:: C++
+
+        void myFunction() {
+            int a = 0;
+            int b = 0;
+            {
+                int tmp = b;
+                b = a;
+                a = tmp;
+            }
+            kernels::kernel( ... );
         }
-        kernels::kernel( ... );
-    }
-    ```
     """
     children = []
     for i, arg in enumerate(args):
@@ -300,6 +392,8 @@ def make_sequence(*args: SequencerArg) -> SfgSequence:
             children.append(arg.resolve())
         elif isinstance(arg, SfgCallTreeNode):
             children.append(arg)
+        elif isinstance(arg, AugExpr):
+            children.append(SfgStatements(str(arg), (), arg.depends))
         elif isinstance(arg, str):
             children.append(SfgStatements(arg, (), ()))
         elif isinstance(arg, tuple):
@@ -312,13 +406,41 @@ def make_sequence(*args: SequencerArg) -> SfgSequence:
     return SfgSequence(children)
 
 
+class SfgInplaceInitBuilder(SfgNodeBuilder):
+    def __init__(self, lhs: SfgVar) -> None:
+        self._lhs: SfgVar = lhs
+        self._depends: set[SfgVar] = set()
+        self._rhs: str | None = None
+
+    def __call__(
+        self,
+        *rhs: str | AugExpr,
+    ) -> SfgInplaceInitBuilder:
+        if self._rhs is not None:
+            raise SfgException("Assignment builder used multiple times.")
+
+        self._rhs = ", ".join(str(expr) for expr in rhs)
+        self._depends = reduce(
+            set.union, (obj.depends for obj in rhs if isinstance(obj, AugExpr)), set()
+        )
+        return self
+
+    def resolve(self) -> SfgCallTreeNode:
+        assert self._rhs is not None
+        return SfgStatements(
+            f"{self._lhs.dtype} {self._lhs.name} {{ {self._rhs} }};",
+            [self._lhs],
+            self._depends,
+        )
+
+
 class SfgBranchBuilder(SfgNodeBuilder):
-    def __init__(self):
+    def __init__(self) -> None:
         self._phase = 0
 
-        self._cond = None
+        self._cond: ExprLike | None = None
         self._branch_true = SfgSequence(())
-        self._branch_false = None
+        self._branch_false: SfgSequence | None = None
 
     def __call__(self, *args) -> SfgBranchBuilder:
         match self._phase:
@@ -328,16 +450,7 @@ class SfgBranchBuilder(SfgNodeBuilder):
                         "Must specify exactly one argument as branch condition!"
                     )
 
-                cond = args[0]
-
-                if isinstance(cond, str):
-                    cond = SfgCustomCondition(cond)
-                elif not isinstance(cond, SfgCondition):
-                    raise ValueError(
-                        "Invalid type for branch condition. Must be either `str` or a subclass of `SfgCondition`."
-                    )
-
-                self._cond = cond
+                self._cond = args[0]
 
             case 1:  # Then-branch
                 self._branch_true = make_sequence(*args)
@@ -352,14 +465,16 @@ class SfgBranchBuilder(SfgNodeBuilder):
 
     def resolve(self) -> SfgCallTreeNode:
         assert self._cond is not None
-        return SfgBranch(self._cond, self._branch_true, self._branch_false)
+        return SfgBranch(
+            make_statements(self._cond), self._branch_true, self._branch_false
+        )
 
 
 class SfgSwitchBuilder(SfgNodeBuilder):
-    def __init__(self, switch_arg: str | TypedSymbolOrObject):
+    def __init__(self, switch_arg: ExprLike):
         self._switch_arg = switch_arg
-        self._cases: dict[str, SfgCallTreeNode] = dict()
-        self._default: SfgCallTreeNode | None = None
+        self._cases: dict[str, SfgSequence] = dict()
+        self._default: SfgSequence | None = None
 
     def case(self, label: str):
         if label in self._cases:
@@ -387,19 +502,7 @@ class SfgSwitchBuilder(SfgNodeBuilder):
         return self
 
     def resolve(self) -> SfgCallTreeNode:
-        return SfgSwitch(self._switch_arg, self._cases, self._default)
-
-
-def parse_include(incl: str | SfgHeaderInclude, private: bool = False):
-    if isinstance(incl, SfgHeaderInclude):
-        return incl
-
-    system_header = False
-    if incl.startswith("<") and incl.endswith(">"):
-        incl = incl[1:-1]
-        system_header = True
-
-    return SfgHeaderInclude(incl, system_header=system_header, private=private)
+        return SfgSwitch(make_statements(self._switch_arg), self._cases, self._default)
 
 
 def struct_from_numpy_dtype(
@@ -415,11 +518,11 @@ def struct_from_numpy_dtype(
     constr_inits = []
 
     for member_name, type_info in fields.items():
-        member_type = SrcType(cpp_typename(type_info[0]))
+        member_type = create_type(type_info[0])
 
         member = SfgMemberVariable(member_name, member_type)
 
-        arg = SrcObject(f"{member_name}_", member_type)
+        arg = SfgVar(f"{member_name}_", member_type)
 
         cls.default.append_member(member)
 
@@ -430,3 +533,15 @@ def struct_from_numpy_dtype(
         cls.default.append_member(SfgConstructor(constr_params, constr_inits))
 
     return cls
+
+
+def _depends(expr: ExprLike | Sequence[ExprLike] | None) -> set[SfgVar]:
+    match expr:
+        case None | str():
+            return set()
+        case SfgVar():
+            return {expr}
+        case AugExpr():
+            return expr.depends
+        case _:
+            raise ValueError(f"Invalid expression: {expr}")
diff --git a/src/pystencilssfg/composer/class_composer.py b/src/pystencilssfg/composer/class_composer.py
index 714ecc839e27e46ceefe370868cdad0886e788a1..63588b7829b2a94122e5c9d7d38770cabce9d6f5 100644
--- a/src/pystencilssfg/composer/class_composer.py
+++ b/src/pystencilssfg/composer/class_composer.py
@@ -1,8 +1,10 @@
 from __future__ import annotations
-from typing import TYPE_CHECKING, Sequence
+from typing import Sequence
 
-from ..tree import SfgCallTreeNode
-from ..source_components import (
+from pystencils.types import PsCustomType, UserTypeSpec
+
+from ..ir import SfgCallTreeNode
+from ..ir.source_components import (
     SfgClass,
     SfgClassMember,
     SfgInClassDefinition,
@@ -12,38 +14,27 @@ from ..source_components import (
     SfgClassKeyword,
     SfgVisibility,
     SfgVisibilityBlock,
+    SfgVar,
 )
-from ..source_concepts import SrcObject
-from ..types import SrcType
 from ..exceptions import SfgException
 
-from .basic_composer import SfgBasicComposer, SfgNodeBuilder, make_sequence
-
-if TYPE_CHECKING:
-    from ..context import SfgContext
+from .mixin import SfgComposerMixIn
+from .basic_composer import SfgNodeBuilder, make_sequence
 
 
-class SfgClassComposer:
+class SfgClassComposer(SfgComposerMixIn):
     """Composer for classes and structs.
 
 
     This class cannot be instantiated on its own but must be mixed in with
-    [SfgBasicComposer][pystencilssfg.composer.SfgBasicComposer].
-    Use through [SfgComposer][pystencilssfg.SfgComposer].
+    :class:`SfgBasicComposer`.
+    Its interface is exposed by :class:`SfgComposer`.
     """
 
-    def __init__(self, ctx: SfgContext):
-        if not isinstance(self, SfgBasicComposer):
-            raise Exception("SfgClassComposer must be mixed-in with SfgBasicComposer.")
-        self._ctx: SfgContext = ctx
-
     class VisibilityContext:
         """Represent a visibility block in the composer syntax.
 
-        Returned by
-        [private][pystencilssfg.composer.SfgClassComposer.private],
-        [public][pystencilssfg.composer.SfgClassComposer.public] and
-        [protected][pystencilssfg.composer.SfgClassComposer.protected].
+        Returned by `private`, `public`, and `protected`.
         """
 
         def __init__(self, visibility: SfgVisibility):
@@ -55,7 +46,7 @@ class SfgClassComposer:
         def __call__(
             self,
             *args: (
-                SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str
+                SfgClassMember | SfgClassComposer.ConstructorBuilder | SfgVar | str
             ),
         ):
             for arg in args:
@@ -69,10 +60,10 @@ class SfgClassComposer:
     class ConstructorBuilder:
         """Composer syntax for constructor building.
 
-        Returned by [constructor][pystencilssfg.composer.SfgClassComposer.constructor].
+        Returned by `constructor`.
         """
 
-        def __init__(self, *params: SrcObject):
+        def __init__(self, *params: SfgVar):
             self._params = params
             self._initializers: list[str] = []
             self._body: str | None = None
@@ -129,16 +120,7 @@ class SfgClassComposer:
         """Create a `private` visibility block in a class or struct body"""
         return SfgClassComposer.VisibilityContext(SfgVisibility.PRIVATE)
 
-    def var(self, name: str, dtype: SrcType):
-        """In a class or struct body or visibility block, and a member variable.
-
-        Args:
-            name: The variable's name
-            dtype: The variable's data type
-        """
-        return SfgMemberVariable(name, dtype)
-
-    def constructor(self, *params: SrcObject):
+    def constructor(self, *params: SfgVar):
         """In a class or struct body or visibility block, add a constructor.
 
         Args:
@@ -149,12 +131,12 @@ class SfgClassComposer:
     def method(
         self,
         name: str,
-        returns: SrcType = SrcType("void"),
+        returns: UserTypeSpec = PsCustomType("void"),
         inline: bool = False,
         const: bool = False,
     ):
         """In a class or struct body or visibility block, add a method.
-        The usage is similar to [SfgBasicComposer.function][pystencilssfg.composer.SfgBasicComposer.function].
+        The usage is similar to :any:`SfgBasicComposer.function`.
 
         Args:
             name: The method name
@@ -166,7 +148,11 @@ class SfgClassComposer:
         def sequencer(*args: str | tuple | SfgCallTreeNode | SfgNodeBuilder):
             tree = make_sequence(*args)
             return SfgMethod(
-                name, tree, return_type=returns, inline=inline, const=const
+                name,
+                tree,
+                return_type=self._composer.cpptype(returns),
+                inline=inline,
+                const=const,
             )
 
         return sequencer
@@ -185,7 +171,7 @@ class SfgClassComposer:
                 SfgClassComposer.VisibilityContext
                 | SfgClassMember
                 | SfgClassComposer.ConstructorBuilder
-                | SrcObject
+                | SfgVar
                 | str
             ),
         ):
@@ -200,7 +186,7 @@ class SfgClassComposer:
                     (
                         SfgClassMember,
                         SfgClassComposer.ConstructorBuilder,
-                        SrcObject,
+                        SfgVar,
                         str,
                     ),
                 ):
@@ -218,9 +204,9 @@ class SfgClassComposer:
 
     @staticmethod
     def _resolve_member(
-        arg: SfgClassMember | SfgClassComposer.ConstructorBuilder | SrcObject | str,
+        arg: SfgClassMember | SfgClassComposer.ConstructorBuilder | SfgVar | str,
     ):
-        if isinstance(arg, SrcObject):
+        if isinstance(arg, SfgVar):
             return SfgMemberVariable(arg.name, arg.dtype)
         elif isinstance(arg, str):
             return SfgInClassDefinition(arg)
diff --git a/src/pystencilssfg/composer/composer.py b/src/pystencilssfg/composer/composer.py
index 3ed79c587ba40dad2215d4dccc851768d7664a50..bba479e3289f2b589a1d32e61997bdaa915eb47e 100644
--- a/src/pystencilssfg/composer/composer.py
+++ b/src/pystencilssfg/composer/composer.py
@@ -11,12 +11,11 @@ if TYPE_CHECKING:
 class SfgComposer(SfgBasicComposer, SfgClassComposer):
     """Primary interface for constructing source files in pystencils-sfg.
 
-    The SfgComposer combines the [SfgBasicComposer][pystencilssfg.composer.SfgBasicComposer]
+    The SfgComposer combines the `SfgBasicComposer`
     for the basic components (kernel namespaces, includes, definitions, and functions)
-    and the [SfgClassComposer][pystencilssfg.composer.SfgClassComposer] for constructing
-    `struct`s and `class`es.
+    and the `SfgClassComposer` for constructing ``struct`` s and ``class`` es.
     """
 
-    def __init__(self, ctx: SfgContext):
-        SfgBasicComposer.__init__(self, ctx)
-        SfgClassComposer.__init__(self, ctx)
+    def __init__(self, sfg: SfgContext | SfgBasicComposer):
+        SfgBasicComposer.__init__(self, sfg)
+        SfgClassComposer.__init__(self)
diff --git a/src/pystencilssfg/composer/mixin.py b/src/pystencilssfg/composer/mixin.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ee8efa61227184686001045bf3f8cb23525bf02
--- /dev/null
+++ b/src/pystencilssfg/composer/mixin.py
@@ -0,0 +1,21 @@
+from __future__ import annotations
+
+from ..context import SfgContext
+from .basic_composer import SfgBasicComposer
+
+
+class SfgComposerMixIn:
+    #   type: ignore
+    def __new__(cls, *args, **kwargs):
+        if not issubclass(cls, SfgBasicComposer):
+            raise Exception(f"{cls} must be mixed-in with SfgBasicComposer.")
+        else:
+            return super().__new__(cls)
+
+    def __init__(self) -> None:
+        self._ctx: SfgContext
+
+    @property
+    def _composer(self) -> SfgBasicComposer:
+        assert isinstance(self, SfgBasicComposer)
+        return self
diff --git a/src/pystencilssfg/configuration.py b/src/pystencilssfg/configuration.py
index 9a8071eb0d7b7088d15175488f3ad3cdef15c74a..2eb3efe7855af24cfe21e2815d7a40d6eea60686 100644
--- a/src/pystencilssfg/configuration.py
+++ b/src/pystencilssfg/configuration.py
@@ -13,9 +13,6 @@ from importlib import util as iutil
 
 from .exceptions import SfgException
 
-HEADER_FILE_EXTENSIONS = {"h", "hpp"}
-IMPL_FILE_EXTENSIONS = {"c", "cpp", ".impl.h"}
-
 
 class SfgConfigSource(Enum):
     DEFAULT = auto()
@@ -53,6 +50,31 @@ class SfgCodeStyle:
         return indent(s, prefix)
 
 
+class SfgOutputMode(Enum):
+    STANDALONE = auto()
+    """Generate a header/implementation file pair (e.g. ``.hpp/.cpp``) where the implementation file will
+    be compiled to a standalone object."""
+
+    INLINE = auto()
+    """Generate a header/inline implementation file pair (e.g. ``.hpp/.ipp``) where all implementations
+    are inlined by including the implementation file at the end of the header file."""
+
+    HEADER_ONLY = auto()
+    """Generate only a header file.
+
+    At the moment, header-only mode does not support generation of kernels and requires that all functions
+    and methods are marked `inline`.
+    """
+
+
+HEADER_FILE_EXTENSIONS = {"h", "hpp", "cuh"}
+IMPL_FILE_EXTENSIONS: dict[SfgOutputMode, set[str]] = {
+    SfgOutputMode.STANDALONE: {"c", "cpp", "cu"},
+    SfgOutputMode.INLINE: {".impl.h", "ipp"},
+    SfgOutputMode.HEADER_ONLY: set(),
+}
+
+
 @dataclass
 class SfgOutputSpec:
     """Name and path specification for files output by the code generator.
@@ -87,36 +109,36 @@ class SfgOutputSpec:
 @dataclass
 class SfgConfiguration:
     """
-    Configuration for the [SfgSourceFileGenerator][pystencilssfg.SourceFileGenerator].
+    Configuration for the `SfgSourceFileGenerator`.
 
     The source file generator draws configuration from a total of four sources:
 
-     - The [default configuration][pystencilssfg.configuration.DEFAULT_CONFIG];
-     - The project configuration;
-     - Command-line arguments;
-     - The user configuration passed to the constructor of `SourceFileGenerator`.
+    - The default configuration (`pystencilssfg.configuration.DEFAULT_CONFIG`);
+    - The project configuration;
+    - Command-line arguments;
+    - The user configuration passed to the constructor of `SourceFileGenerator`.
 
     They take precedence in the following way:
 
-     - Project configuration overrides the default configuration
-     - Command line arguments override the project configuration
-     - User configuration overrides default and project configuration,
-       and must not conflict with command-line arguments; otherwise, an error is thrown.
+    - Project configuration overrides the default configuration
+    - Command line arguments override the project configuration
+    - User configuration overrides default and project configuration,
+      and must not conflict with command-line arguments; otherwise, an error is thrown.
 
-    ### Project Configuration via Configurator Script
+    **Project Configuration via Configurator Script**
 
     Currently, the only way to define the project configuration is via a configuration module.
     A configurator module is a Python file defining the following function at the top-level:
 
-    ```Python
-    from pystencilssfg import SfgConfiguration
+    .. code-block:: Python
+
+        from pystencilssfg import SfgConfiguration
 
-    def sfg_config() -> SfgConfiguration:
-        # ...
-        return SfgConfiguration(
+        def sfg_config() -> SfgConfiguration:
             # ...
-        )
-    ```
+            return SfgConfiguration(
+                # ...
+            )
 
     The configuration module is passed to the code generation script via the command-line argument
     `--sfg-config-module`.
@@ -130,12 +152,12 @@ class SfgConfiguration:
     impl_extension: str | None = None
     """File extension for generated implementation file."""
 
-    header_only: bool | None = None
-    """If set to `True`, generate only a header file without accompaning source file."""
+    output_mode: SfgOutputMode | None = None
+    """The generator's output mode; defines which files to generate, and the set of legal file extensions."""
 
     outer_namespace: str | None = None
     """The outermost namespace in the generated file. May be a valid C++ nested namespace qualifier
-    (like `a::b::c`) or `None` if no outer namespace should be generated."""
+    (like ``a::b::c``) or `None` if no outer namespace should be generated."""
 
     codestyle: SfgCodeStyle | None = None
     """Code style that should be used by the code generator."""
@@ -147,11 +169,6 @@ class SfgConfiguration:
     """Object for managing project-specific information. To be set by the configurator script."""
 
     def __post_init__(self, cfg_src: SfgConfigSource | None = None):
-        if self.header_only:
-            raise SfgConfigException(
-                cfg_src, "Header-only code generation is not implemented yet."
-            )
-
         if self.header_extension and self.header_extension[0] == ".":
             self.header_extension = self.header_extension[1:]
 
@@ -178,12 +195,12 @@ DEFAULT_CONFIG = SfgConfiguration(
     config_source=SfgConfigSource.DEFAULT,
     header_extension="h",
     impl_extension="cpp",
-    header_only=False,
+    output_mode=SfgOutputMode.STANDALONE,
     outer_namespace=None,
     codestyle=SfgCodeStyle(),
     output_directory=".",
 )
-"""Default configuration for the [SourceFileGenerator][pystencilssfg.SourceFileGenerator]."""
+"""Default configuration for the `SourceFileGenerator`."""
 
 
 def run_configurator(configurator_script: str):
@@ -230,7 +247,11 @@ def add_config_args_to_parser(parser: ArgumentParser):
         help="Comma-separated list of file extensions",
     )
     config_group.add_argument(
-        "--sfg-header-only", default=None, action="store_true", dest="header_only"
+        "--sfg-output-mode",
+        type=str,
+        default=None,
+        choices=("standalone", "inline", "header-only"),
+        dest="output_mode",
     )
     config_group.add_argument(
         "--sfg-config-module", type=str, default=None, dest="configurator_script"
@@ -245,10 +266,23 @@ def config_from_parser_args(args):
     else:
         project_config = None
 
+    if args.output_mode is not None:
+        match args.output_mode.lower():
+            case "standalone":
+                output_mode = SfgOutputMode.STANDALONE
+            case "inline":
+                output_mode = SfgOutputMode.INLINE
+            case "header-only":
+                output_mode = SfgOutputMode.HEADER_ONLY
+            case _:
+                assert False, "invalid output mode"
+    else:
+        output_mode = None
+
     if args.file_extensions is not None:
         file_extentions = list(args.file_extensions.split(","))
         h_ext, src_ext = _get_file_extensions(
-            SfgConfigSource.COMMANDLINE, file_extentions
+            SfgConfigSource.COMMANDLINE, file_extentions, output_mode
         )
     else:
         h_ext, src_ext = None, None
@@ -257,7 +291,7 @@ def config_from_parser_args(args):
         config_source=SfgConfigSource.COMMANDLINE,
         header_extension=h_ext,
         impl_extension=src_ext,
-        header_only=args.header_only,
+        output_mode=output_mode,
         output_directory=args.output_directory,
     )
 
@@ -312,7 +346,9 @@ def merge_configurations(
     return config
 
 
-def _get_file_extensions(cfgsrc: SfgConfigSource, extensions: Sequence[str]):
+def _get_file_extensions(
+    cfgsrc: SfgConfigSource, extensions: Sequence[str], output_mode: SfgOutputMode
+):
     h_ext = None
     src_ext = None
 
@@ -325,7 +361,7 @@ def _get_file_extensions(cfgsrc: SfgConfigSource, extensions: Sequence[str]):
                     cfgsrc, "Multiple header file extensions specified."
                 )
             h_ext = ext
-        elif ext in IMPL_FILE_EXTENSIONS:
+        elif ext in IMPL_FILE_EXTENSIONS[output_mode]:
             if src_ext is not None:
                 raise SfgConfigException(
                     cfgsrc, "Multiple source file extensions specified."
@@ -333,7 +369,7 @@ def _get_file_extensions(cfgsrc: SfgConfigSource, extensions: Sequence[str]):
             src_ext = ext
         else:
             raise SfgConfigException(
-                cfgsrc, f"Don't know how to interpret file extension '.{ext}'"
+                cfgsrc, f"Invalid file extension '.{ext}' for output mode {output_mode}"
             )
 
     return h_ext, src_ext
diff --git a/src/pystencilssfg/context.py b/src/pystencilssfg/context.py
index 5dcf35e5b76d82f6e54928bd23dde3b0485cec5c..69ad83f8e2ab8ddf9436e1ebc3505b819d6a5cf2 100644
--- a/src/pystencilssfg/context.py
+++ b/src/pystencilssfg/context.py
@@ -1,7 +1,7 @@
 from typing import Generator, Sequence, Any
 
 from .configuration import SfgCodeStyle
-from .source_components import (
+from .ir.source_components import (
     SfgHeaderInclude,
     SfgKernelNamespace,
     SfgFunction,
@@ -13,34 +13,33 @@ from .exceptions import SfgException
 class SfgContext:
     """Represents a header/implementation file pair in the code generator.
 
-    ## Source File Properties and Components
+    **Source File Properties and Components**
 
     The SfgContext collects all properties and components of a header/implementation
     file pair (or just the header file, if header-only generation is used).
     These are:
 
-     - The code namespace, which is combined from the [outer_namespace][pystencilssfg.SfgContext.outer_namespace]
-       and the [inner_namespace][pystencilssfg.SfgContext.inner_namespace]. The outer namespace is meant to be set
-       externally e.g. by the project configuration, while the inner namespace is meant to be set by the generator
-       script.
-     - The [prelude comment][pystencilssfg.SfgContext.prelude_comment] is a block of text printed as a comment block
-       at the top of both generated files. Typically, it contains authorship and licence information.
-     - The set of [Included header files][pystencilssfg.SfgContext.includes].
-     - Custom [definitions][pystencilssfg.SfgContext.definitions], which are just arbitrary code strings.
-     - Any number of [kernel namespaces][pystencilssfg.SfgContext.kernel_namespaces], within which *pystencils*
-       kernels are managed.
-     - Any number of [functions][pystencilssfg.SfgContext.functions], which are meant to serve as wrappers
-       around kernel calls.
-     - Any number of [classes][pystencilssfg.SfgContext.classes], which can be used to build more extensive wrappers
-       around kernels.
-
-    ## Order of Definitions
+    - The code namespace, which is combined from the `outer_namespace`
+      and the `pystencilssfg.SfgContext.inner_namespace`. The outer namespace is meant to be set
+      externally e.g. by the project configuration, while the inner namespace is meant to be set by the generator
+      script.
+    - The `prelude comment` is a block of text printed as a comment block
+      at the top of both generated files. Typically, it contains authorship and licence information.
+    - The set of included header files (`pystencilssfg.SfgContext.includes`).
+    - Custom `definitions`, which are just arbitrary code strings.
+    - Any number of kernel namespaces (`pystencilssfg.SfgContext.kernel_namespaces`), within which *pystencils*
+      kernels are managed.
+    - Any number of functions (`pystencilssfg.SfgContext.functions`), which are meant to serve as wrappers
+      around kernel calls.
+    - Any number of classes (`pystencilssfg.SfgContext.classes`), which can be used to build more extensive wrappers
+      around kernels.
+
+    **Order of Definitions**
 
     To honor C/C++ use-after-declare rules, the context preserves the order in which definitions, functions and classes
     are added to it.
     The header file printers implemented in *pystencils-sfg* will print the declarations accordingly.
-    The declarations can retrieved in order of definition via
-    [declarations_ordered][pystencilssfg.SfgContext.declarations_ordered].
+    The declarations can retrieved in order of definition via `declarations_ordered`.
     """
 
     def __init__(
diff --git a/src/pystencilssfg/emission/__init__.py b/src/pystencilssfg/emission/__init__.py
index d7478e0381b8593ebc3a02564045e38852e7b6d9..3280e459ff72a23bea06e711e5fb0aa73989cc74 100644
--- a/src/pystencilssfg/emission/__init__.py
+++ b/src/pystencilssfg/emission/__init__.py
@@ -1,3 +1,5 @@
+from .emitter import AbstractEmitter
 from .header_impl_pair import HeaderImplPairEmitter
+from .header_only import HeaderOnlyEmitter
 
-__all__ = ["HeaderImplPairEmitter"]
+__all__ = ["AbstractEmitter", "HeaderImplPairEmitter", "HeaderOnlyEmitter"]
diff --git a/src/pystencilssfg/emission/emitter.py b/src/pystencilssfg/emission/emitter.py
new file mode 100644
index 0000000000000000000000000000000000000000..55fe43c337069e49d38ed27a85e2f33929dfdc54
--- /dev/null
+++ b/src/pystencilssfg/emission/emitter.py
@@ -0,0 +1,15 @@
+from typing import Sequence
+from abc import ABC, abstractmethod
+
+from ..context import SfgContext
+
+
+class AbstractEmitter(ABC):
+    @property
+    @abstractmethod
+    def output_files(self) -> Sequence[str]:
+        pass
+
+    @abstractmethod
+    def write_files(self, ctx: SfgContext):
+        pass
diff --git a/src/pystencilssfg/emission/header_impl_pair.py b/src/pystencilssfg/emission/header_impl_pair.py
index 53b6857c5fd7ebc098f72efbf2bb68514a4deafc..8d2cd2cf437e0dfbfa8527d6f288d291c27e0a83 100644
--- a/src/pystencilssfg/emission/header_impl_pair.py
+++ b/src/pystencilssfg/emission/header_impl_pair.py
@@ -1,3 +1,4 @@
+from typing import Sequence
 from os import path, makedirs
 
 from ..configuration import SfgOutputSpec
@@ -6,21 +7,24 @@ from .prepare import prepare_context
 from .printers import SfgHeaderPrinter, SfgImplPrinter
 from .clang_format import invoke_clang_format
 
+from .emitter import AbstractEmitter
 
-class HeaderImplPairEmitter:
+
+class HeaderImplPairEmitter(AbstractEmitter):
     """Emits a header-implementation file pair."""
 
-    def __init__(self, output_spec: SfgOutputSpec):
+    def __init__(self, output_spec: SfgOutputSpec, inline_impl: bool = False):
         """Create a `HeaderImplPairEmitter` from an [SfgOutputSpec][pystencilssfg.configuration.SfgOutputSpec]."""
         self._basename = output_spec.basename
         self._output_directory = output_spec.output_directory
         self._header_filename = output_spec.get_header_filename()
         self._impl_filename = output_spec.get_impl_filename()
+        self._inline_impl = inline_impl
 
         self._ospec = output_spec
 
     @property
-    def output_files(self) -> tuple[str, str]:
+    def output_files(self) -> Sequence[str]:
         """The files that will be written by `write_files`."""
         return (
             path.join(self._output_directory, self._header_filename),
@@ -32,8 +36,8 @@ class HeaderImplPairEmitter:
         specified by the output specification."""
         ctx = prepare_context(ctx)
 
-        header_printer = SfgHeaderPrinter(ctx, self._ospec)
-        impl_printer = SfgImplPrinter(ctx, self._ospec)
+        header_printer = SfgHeaderPrinter(ctx, self._ospec, self._inline_impl)
+        impl_printer = SfgImplPrinter(ctx, self._ospec, self._inline_impl)
 
         header = header_printer.get_code()
         impl = impl_printer.get_code()
diff --git a/src/pystencilssfg/emission/header_only.py b/src/pystencilssfg/emission/header_only.py
new file mode 100644
index 0000000000000000000000000000000000000000..7347a61059ac48ad2f5b7c320aa3a2965c5b8452
--- /dev/null
+++ b/src/pystencilssfg/emission/header_only.py
@@ -0,0 +1,37 @@
+from typing import Sequence
+from os import path, makedirs
+
+from ..configuration import SfgOutputSpec
+from ..context import SfgContext
+from .prepare import prepare_context
+from .printers import SfgHeaderPrinter
+from .clang_format import invoke_clang_format
+
+from .emitter import AbstractEmitter
+
+
+class HeaderOnlyEmitter(AbstractEmitter):
+    def __init__(self, output_spec: SfgOutputSpec):
+        """Create a `HeaderImplPairEmitter` from an [SfgOutputSpec][pystencilssfg.configuration.SfgOutputSpec]."""
+        self._basename = output_spec.basename
+        self._output_directory = output_spec.output_directory
+        self._header_filename = output_spec.get_header_filename()
+
+        self._ospec = output_spec
+
+    @property
+    def output_files(self) -> Sequence[str]:
+        """The files that will be written by `write_files`."""
+        return (path.join(self._output_directory, self._header_filename),)
+
+    def write_files(self, ctx: SfgContext):
+        ctx = prepare_context(ctx)
+
+        header_printer = SfgHeaderPrinter(ctx, self._ospec)
+        header = header_printer.get_code()
+        header = invoke_clang_format(header, ctx.codestyle)
+
+        makedirs(self._output_directory, exist_ok=True)
+
+        with open(self._ospec.get_header_filepath(), "w") as headerfile:
+            headerfile.write(header)
diff --git a/src/pystencilssfg/emission/prepare.py b/src/pystencilssfg/emission/prepare.py
index 28493bbb99abecad9dc0fbaf62d0cb1582373fac..55e7fe035d0561f2579f7badc6f7588ac243ab4e 100644
--- a/src/pystencilssfg/emission/prepare.py
+++ b/src/pystencilssfg/emission/prepare.py
@@ -1,6 +1,74 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from functools import reduce
+
+from ..exceptions import SfgException
+from ..ir import SfgCallTreeNode
+from ..ir.source_components import (
+    SfgFunction,
+    SfgClass,
+    SfgConstructor,
+    SfgMemberVariable,
+    SfgInClassDefinition,
+)
 from ..context import SfgContext
 
-from ..visitors import CollectIncludes
+if TYPE_CHECKING:
+    from ..ir.source_components import SfgHeaderInclude
+
+
+class CollectIncludes:
+    def __call__(self, obj: object) -> set[SfgHeaderInclude]:
+        return self.visit(obj)
+
+    def visit(self, obj: object) -> set[SfgHeaderInclude]:
+        match obj:
+            case SfgContext():
+                includes = set()
+                for func in obj.functions():
+                    includes |= self.visit(func)
+
+                for cls in obj.classes():
+                    includes |= self.visit(cls)
+
+                return includes
+
+            case SfgCallTreeNode():
+                return reduce(
+                    lambda accu, child: accu | self.visit(child),
+                    obj.children,
+                    obj.required_includes,
+                )
+
+            case SfgFunction(_, tree, _):
+                return self.visit(tree)
+
+            case SfgClass():
+                return reduce(
+                    lambda accu, member: accu | (self.visit(member)),
+                    obj.members(),
+                    set(),
+                )
+
+            case SfgConstructor():
+                return reduce(
+                    lambda accu, obj: accu | obj.required_includes,
+                    obj.parameters,
+                    set(),
+                )
+
+            case SfgMemberVariable():
+                return obj.required_includes
+
+            case SfgInClassDefinition():
+                return set()
+
+            case _:
+                raise SfgException(
+                    f"Can't collect includes from object of type {type(obj)}"
+                )
 
 
 def prepare_context(ctx: SfgContext):
diff --git a/src/pystencilssfg/emission/printers.py b/src/pystencilssfg/emission/printers.py
index 5c86d5f5527805b26a2733d8e1f545afeb455ec4..1b3a805cdb3efbac12e81005b7747859d0e4e2cb 100644
--- a/src/pystencilssfg/emission/printers.py
+++ b/src/pystencilssfg/emission/printers.py
@@ -3,16 +3,15 @@ from __future__ import annotations
 from textwrap import indent
 from itertools import chain, repeat, cycle
 
-from pystencils.astnodes import KernelFunction
-from pystencils import Backend
-from pystencils.backends import generate_c
+from pystencils import KernelFunction
+from pystencils.backend.emission import emit_code
 
 from ..context import SfgContext
 from ..configuration import SfgOutputSpec
 from ..visitors import visitor
 from ..exceptions import SfgException
 
-from ..source_components import (
+from ..ir.source_components import (
     SfgEmptyLines,
     SfgHeaderInclude,
     SfgKernelNamespace,
@@ -71,9 +70,12 @@ class SfgGeneralPrinter:
 
 
 class SfgHeaderPrinter(SfgGeneralPrinter):
-    def __init__(self, ctx: SfgContext, output_spec: SfgOutputSpec):
+    def __init__(
+        self, ctx: SfgContext, output_spec: SfgOutputSpec, inline_impl: bool = False
+    ):
         self._output_spec = output_spec
         self._ctx = ctx
+        self._inline_impl = inline_impl
 
     def get_code(self) -> str:
         return self.visit(self._ctx)
@@ -103,6 +105,9 @@ class SfgHeaderPrinter(SfgGeneralPrinter):
         if fq_namespace is not None:
             code += f"}} // namespace {fq_namespace}\n"
 
+        if self._inline_impl:
+            code += f'#include "{self._output_spec.get_impl_filename()}"\n'
+
         return code
 
     @visit.case(SfgFunction)
@@ -182,9 +187,12 @@ def delimiter(content):
 
 
 class SfgImplPrinter(SfgGeneralPrinter):
-    def __init__(self, ctx: SfgContext, output_spec: SfgOutputSpec):
+    def __init__(
+        self, ctx: SfgContext, output_spec: SfgOutputSpec, inline_impl: bool = False
+    ):
         self._output_spec = output_spec
         self._ctx = ctx
+        self._inline_impl = inline_impl
 
     def get_code(self) -> str:
         return self.visit(self._ctx)
@@ -197,7 +205,8 @@ class SfgImplPrinter(SfgGeneralPrinter):
     def frame(self, ctx: SfgContext) -> str:
         code = super().prelude(ctx)
 
-        code += f'\n#include "{self._output_spec.get_header_filename()}"\n\n'
+        if not self._inline_impl:
+            code += f'\n#include "{self._output_spec.get_header_filename()}"\n\n'
 
         includes = filter(lambda incl: incl.private, ctx.includes())
         code += "\n".join(self.visit(incl) for incl in includes)
@@ -230,17 +239,20 @@ class SfgImplPrinter(SfgGeneralPrinter):
     @visit.case(SfgKernelNamespace)
     def kernel_namespace(self, kns: SfgKernelNamespace) -> str:
         code = f"namespace {kns.name} {{\n\n"
-        code += "\n\n".join(self.visit(ast) for ast in kns.asts)
+        code += "\n\n".join(self.visit(ast) for ast in kns.kernel_functions)
         code += f"\n}} // namespace {kns.name}\n"
         return code
 
     @visit.case(KernelFunction)
     def kernel(self, kfunc: KernelFunction) -> str:
-        return generate_c(kfunc, dialect=Backend.C)
+        return emit_code(kfunc)
 
     @visit.case(SfgFunction)
     def function(self, func: SfgFunction) -> str:
-        code = f"{func.return_type} {func.name} ({self.param_list(func)})"
+        inline_prefix = "inline " if self._inline_impl else ""
+        code = (
+            f"{inline_prefix} {func.return_type} {func.name} ({self.param_list(func)})"
+        )
         code += (
             "{\n" + self._ctx.codestyle.indent(func.tree.get_code(self._ctx)) + "}\n"
         )
@@ -253,8 +265,9 @@ class SfgImplPrinter(SfgGeneralPrinter):
 
     @visit.case(SfgMethod)
     def sfg_method(self, method: SfgMethod) -> str:
+        inline_prefix = "inline " if self._inline_impl else ""
         const_qual = "const" if method.const else ""
-        code = f"{method.return_type} {method.owning_class.class_name}::{method.name}"
+        code = f"{inline_prefix}{method.return_type} {method.owning_class.class_name}::{method.name}"
         code += f"({self.param_list(method)}) {const_qual}"
         code += (
             " {\n" + self._ctx.codestyle.indent(method.tree.get_code(self._ctx)) + "}\n"
diff --git a/src/pystencilssfg/extensions/__init__.py b/src/pystencilssfg/extensions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..098690f34561fc696afe94c5ee7da0b79c32a761
--- /dev/null
+++ b/src/pystencilssfg/extensions/__init__.py
@@ -0,0 +1,3 @@
+from .sycl import SyclComposer
+
+__all__ = ["SyclComposer"]
diff --git a/src/pystencilssfg/extensions/sycl.py b/src/pystencilssfg/extensions/sycl.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc3f83fa6a049febd76d1e62dcdda39d4a502575
--- /dev/null
+++ b/src/pystencilssfg/extensions/sycl.py
@@ -0,0 +1,270 @@
+from __future__ import annotations
+from typing import Sequence
+from enum import Enum
+import re
+
+from pystencils.types import PsType, PsCustomType
+from pystencils.enums import Target
+from pystencils.backend.kernelfunction import KernelParameter
+
+from ..exceptions import SfgException
+from ..context import SfgContext
+from ..composer import (
+    SfgBasicComposer,
+    SfgClassComposer,
+    SfgComposer,
+    SfgComposerMixIn,
+)
+from ..ir.source_components import SfgKernelHandle, SfgHeaderInclude
+from ..ir.source_components import SfgVar, SfgSymbolLike
+from ..ir import (
+    SfgCallTreeNode,
+    SfgCallTreeLeaf,
+    SfgKernelCallNode,
+)
+
+from ..lang import AugExpr
+
+
+class SyclComposerMixIn(SfgComposerMixIn):
+    """Composer mix-in for SYCL code generation"""
+
+    def sycl_handler(self, name: str) -> SyclHandler:
+        """Obtain a `SyclHandler`, which represents a ``sycl::handler`` object."""
+        return SyclHandler(self._ctx).var(name)
+
+    def sycl_group(self, dims: int, name: str) -> SyclGroup:
+        """Obtain a `SyclHandler`, which represents a ``sycl::handler`` object."""
+        return SyclGroup(dims, self._ctx).var(name)
+
+    def sycl_range(self, dims: int, name: str, ref: bool = False) -> SfgVar:
+        ref_str = " &" if ref else ""
+        return SfgVar(name, PsCustomType(f"sycl::range< {dims} >{ref_str}"))
+
+
+class SyclComposer(SfgBasicComposer, SfgClassComposer, SyclComposerMixIn):
+    """Composer extension providing SYCL code generation capabilities"""
+
+    def __init__(self, sfg: SfgContext | SfgComposer):
+        super().__init__(sfg)
+
+
+class SyclHandler(AugExpr):
+    """Represents a SYCL command group handler (``sycl::handler``)."""
+
+    def __init__(self, ctx: SfgContext):
+        dtype = PsCustomType("sycl::handler &")
+        super().__init__(dtype)
+
+        self._ctx = ctx
+
+    def parallel_for(self, range: SfgVar | Sequence[int], kernel: SfgKernelHandle):
+        """Generate a ``parallel_for`` kernel invocation using this command group handler.
+
+        Args:
+            range: Object, or tuple of integers, indicating the kernel's iteration range
+            kernel: Handle to the pystencils-kernel to be executed
+        """
+        self._ctx.add_include(SfgHeaderInclude("sycl/sycl.hpp", system_header=True))
+
+        kfunc = kernel.get_kernel_function()
+        if kfunc.target != Target.SYCL:
+            raise SfgException(
+                f"Kernel given to `parallel_for` is no SYCL kernel: {kernel.kernel_name}"
+            )
+
+        id_regex = re.compile(r"sycl::(id|item|nd_item)<\s*[0-9]\s*>")
+
+        def filter_id(param: SfgSymbolLike[KernelParameter]) -> bool:
+            return (
+                isinstance(param.dtype, PsCustomType)
+                and id_regex.search(param.dtype.c_string()) is not None
+            )
+
+        id_param = list(filter(filter_id, kernel.scalar_parameters))[0]
+
+        tree = SfgKernelCallNode(kernel)
+
+        kernel_lambda = SfgLambda(("=",), (id_param,), tree, None)
+        return SyclKernelInvoke(self, SyclInvokeType.ParallelFor, range, kernel_lambda)
+
+
+class SyclGroup(AugExpr):
+    """Represents a SYCL group (``sycl::group``)."""
+
+    def __init__(self, dimensions: int, ctx: SfgContext):
+        dtype = PsCustomType(f"sycl::group< {dimensions} > &")
+        super().__init__(dtype)
+
+        self._dimensions = dimensions
+        self._ctx = ctx
+
+    def parallel_for_work_item(
+        self, range: SfgVar | Sequence[int], kernel: SfgKernelHandle
+    ):
+        """Generate a ``parallel_for_work_item` kernel invocation on this group.`
+
+        Args:
+            range: Object, or tuple of integers, indicating the kernel's iteration range
+            kernel: Handle to the pystencils-kernel to be executed
+        """
+
+        self._ctx.add_include(SfgHeaderInclude("sycl/sycl.hpp", system_header=True))
+
+        kfunc = kernel.get_kernel_function()
+        if kfunc.target != Target.SYCL:
+            raise SfgException(
+                f"Kernel given to `parallel_for` is no SYCL kernel: {kernel.kernel_name}"
+            )
+
+        id_regex = re.compile(r"sycl::id<\s*[0-9]\s*>")
+
+        def filter_id(param: SfgSymbolLike[KernelParameter]) -> bool:
+            return (
+                isinstance(param.dtype, PsCustomType)
+                and id_regex.search(param.dtype.c_string()) is not None
+            )
+
+        id_param = list(filter(filter_id, kernel.scalar_parameters))[0]
+        h_item = SfgVar("item", PsCustomType("sycl::h_item< 3 >"))
+
+        comp = SfgComposer(self._ctx)
+        tree = comp.seq(
+            comp.map_param(
+                id_param,
+                h_item,
+                f"{id_param.dtype} {id_param.name} = {h_item}.get_local_id();",
+            ),
+            SfgKernelCallNode(kernel),
+        )
+
+        kernel_lambda = SfgLambda(("=",), (h_item,), tree, None)
+        return SyclKernelInvoke(
+            self, SyclInvokeType.ParallelForWorkItem, range, kernel_lambda
+        )
+
+
+class SfgLambda:
+    """Models a C++ lambda expression"""
+
+    def __init__(
+        self,
+        captures: Sequence[str],
+        params: Sequence[SfgVar],
+        tree: SfgCallTreeNode,
+        return_type: PsType | None = None,
+    ) -> None:
+        self._captures = tuple(captures)
+        self._params = tuple(params)
+        self._tree = tree
+        self._return_type = return_type
+
+        from ..ir.postprocessing import CallTreePostProcessing
+
+        postprocess = CallTreePostProcessing()
+        self._required_params = postprocess(self._tree).function_params - set(
+            self._params
+        )
+
+    @property
+    def captures(self) -> tuple[str, ...]:
+        return self._captures
+
+    @property
+    def parameters(self) -> tuple[SfgVar, ...]:
+        return self._params
+
+    @property
+    def body(self) -> SfgCallTreeNode:
+        return self._tree
+
+    @property
+    def return_type(self) -> PsType | None:
+        return self._return_type
+
+    @property
+    def required_parameters(self) -> set[SfgVar]:
+        return self._required_params
+
+    def get_code(self, ctx: SfgContext):
+        captures = ", ".join(self._captures)
+        params = ", ".join(f"{p.dtype} {p.name}" for p in self._params)
+        body = self._tree.get_code(ctx)
+        body = ctx.codestyle.indent(body)
+        rtype = (
+            f"-> {self._return_type.c_string()} "
+            if self._return_type is not None
+            else ""
+        )
+
+        return f"[{captures}] ({params}) {rtype}{{\n{body}\n}}"
+
+
+class SyclInvokeType(Enum):
+    ParallelFor = ("parallel_for", SyclHandler)
+    ParallelForWorkItem = ("parallel_for_work_item", SyclGroup)
+
+    @property
+    def method(self) -> str:
+        return self.value[0]
+
+    @property
+    def invoker_class(self) -> type:
+        return self.value[1]
+
+
+class SyclKernelInvoke(SfgCallTreeLeaf):
+    """A SYCL kernel invocation on a given handler or group"""
+
+    def __init__(
+        self,
+        invoker: SyclHandler | SyclGroup,
+        invoke_type: SyclInvokeType,
+        range: SfgVar | Sequence[int],
+        lamb: SfgLambda,
+    ):
+        if not isinstance(invoker, invoke_type.invoker_class):
+            raise SfgException(
+                f"Cannot invoke kernel via `{invoke_type.method}` on a {type(invoker)}"
+            )
+
+        super().__init__()
+        self._invoker = invoker
+        self._invoke_type = invoke_type
+        self._range: SfgVar | tuple[int, ...] = (
+            range if isinstance(range, SfgVar) else tuple(range)
+        )
+        self._lambda = lamb
+
+        self._required_params = invoker.depends | lamb.required_parameters
+
+        if isinstance(range, SfgVar):
+            self._required_params.add(range)
+
+    @property
+    def invoker(self) -> SyclHandler | SyclGroup:
+        return self._invoker
+
+    @property
+    def range(self) -> SfgVar | tuple[int, ...]:
+        return self._range
+
+    @property
+    def kernel(self) -> SfgLambda:
+        return self._lambda
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return self._required_params
+
+    def get_code(self, ctx: SfgContext) -> str:
+        if isinstance(self._range, SfgVar):
+            range_code = self._range.name
+        else:
+            range_code = "{ " + ", ".join(str(r) for r in self._range) + " }"
+
+        kernel_code = self._lambda.get_code(ctx)
+        invoker = str(self._invoker)
+        method = self._invoke_type.method
+
+        return f"{invoker}.{method}({range_code}, {kernel_code});"
diff --git a/src/pystencilssfg/generator.py b/src/pystencilssfg/generator.py
index 9e5f0fbbdb9a32d5e76818dddf30c81f46821464..22db7e2cc54d0670bd4a378dae88c95559d04e18 100644
--- a/src/pystencilssfg/generator.py
+++ b/src/pystencilssfg/generator.py
@@ -7,14 +7,33 @@ from os import path
 
 from .configuration import (
     SfgConfiguration,
+    SfgOutputMode,
     config_from_commandline,
     merge_configurations,
 )
 from .context import SfgContext
+from .composer import SfgComposer
+from .emission import AbstractEmitter
 
 
 class SourceFileGenerator:
-    """Context manager that controls the code generation process in generator scripts."""
+    """Context manager that controls the code generation process in generator scripts.
+
+    **Usage:** The `SourceFileGenerator` must be used as a context manager by calling it within
+    a ``with`` statement in the top-level code of a generator script (see :ref:`guide:generator_scripts`).
+    Upon entry to its context, it creates an :class:`SfgComposer` which can be used to populate the generated files.
+    When the managed region finishes, the code files are generated and written to disk at the locations
+    defined by the configuration.
+    Existing copies of the target files are deleted on entry to the managed region,
+    and if an exception occurs within the managed region, no files are exported.
+
+    **Configuration:** The `SourceFileGenerator` optionally takes a user-defined configuration
+    object which is merged with configuration obtained from the build system; for details
+    on configuration sources, refer to :class:`SfgConfiguration`.
+
+    Args:
+        sfg_config: User configuration for the code generator
+    """
 
     def __init__(self, sfg_config: SfgConfiguration | None = None):
         if sfg_config and not isinstance(sfg_config, SfgConfiguration):
@@ -39,18 +58,31 @@ class SourceFileGenerator:
             project_info=config.project_info,
         )
 
-        from .emission import HeaderImplPairEmitter
+        self._emitter: AbstractEmitter
+        match config.output_mode:
+            case SfgOutputMode.HEADER_ONLY:
+                from .emission import HeaderOnlyEmitter
+
+                self._emitter = HeaderOnlyEmitter(config.get_output_spec(basename))
+            case SfgOutputMode.INLINE:
+                from .emission import HeaderImplPairEmitter
+
+                self._emitter = HeaderImplPairEmitter(
+                    config.get_output_spec(basename), inline_impl=True
+                )
+            case SfgOutputMode.STANDALONE:
+                from .emission import HeaderImplPairEmitter
 
-        self._emitter = HeaderImplPairEmitter(config.get_output_spec(basename))
+                self._emitter = HeaderImplPairEmitter(config.get_output_spec(basename))
 
     def clean_files(self):
         for file in self._emitter.output_files:
             if path.exists(file):
                 os.remove(file)
 
-    def __enter__(self) -> SfgContext:
+    def __enter__(self) -> SfgComposer:
         self.clean_files()
-        return self._context
+        return SfgComposer(self._context)
 
     def __exit__(self, exc_type, exc_value, traceback):
         if exc_type is None:
diff --git a/src/pystencilssfg/ir/__init__.py b/src/pystencilssfg/ir/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..43660069ce5e049f60b42193698427f1756d2fae
--- /dev/null
+++ b/src/pystencilssfg/ir/__init__.py
@@ -0,0 +1,66 @@
+from .call_tree import (
+    SfgCallTreeNode,
+    SfgCallTreeLeaf,
+    SfgEmptyNode,
+    SfgKernelCallNode,
+    SfgCudaKernelInvocation,
+    SfgBlock,
+    SfgSequence,
+    SfgStatements,
+    SfgFunctionParams,
+    SfgRequireIncludes,
+    SfgBranch,
+    SfgSwitchCase,
+    SfgSwitch,
+)
+
+from .source_components import (
+    SfgHeaderInclude,
+    SfgEmptyLines,
+    SfgKernelNamespace,
+    SfgKernelHandle,
+    SfgVar,
+    SfgSymbolLike,
+    SfgFunction,
+    SfgVisibility,
+    SfgClassKeyword,
+    SfgClassMember,
+    SfgVisibilityBlock,
+    SfgInClassDefinition,
+    SfgMemberVariable,
+    SfgMethod,
+    SfgConstructor,
+    SfgClass,
+)
+
+__all__ = [
+    "SfgCallTreeNode",
+    "SfgCallTreeLeaf",
+    "SfgEmptyNode",
+    "SfgKernelCallNode",
+    "SfgCudaKernelInvocation",
+    "SfgSequence",
+    "SfgBlock",
+    "SfgStatements",
+    "SfgFunctionParams",
+    "SfgRequireIncludes",
+    "SfgBranch",
+    "SfgSwitchCase",
+    "SfgSwitch",
+    "SfgHeaderInclude",
+    "SfgEmptyLines",
+    "SfgKernelNamespace",
+    "SfgKernelHandle",
+    "SfgVar",
+    "SfgSymbolLike",
+    "SfgFunction",
+    "SfgVisibility",
+    "SfgClassKeyword",
+    "SfgClassMember",
+    "SfgVisibilityBlock",
+    "SfgInClassDefinition",
+    "SfgMemberVariable",
+    "SfgMethod",
+    "SfgConstructor",
+    "SfgClass",
+]
diff --git a/src/pystencilssfg/ir/call_tree.py b/src/pystencilssfg/ir/call_tree.py
new file mode 100644
index 0000000000000000000000000000000000000000..34d50182b7bca8348a161553e4f7edaa4ae3c0d0
--- /dev/null
+++ b/src/pystencilssfg/ir/call_tree.py
@@ -0,0 +1,419 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING, Sequence, Iterable, NewType
+
+from abc import ABC, abstractmethod
+from itertools import chain
+
+from .source_components import SfgHeaderInclude, SfgKernelHandle, SfgVar
+
+if TYPE_CHECKING:
+    from ..context import SfgContext
+
+
+class SfgCallTreeNode(ABC):
+    """Base class for all nodes comprising SFG call trees.
+
+    ## Code Printing
+
+    For extensibility, code printing is implemented inside the call tree.
+    Therefore, every instantiable call tree node must implement the method `get_code`.
+    By convention, the string returned by `get_code` should not contain a trailing newline.
+
+    ## Branching Structure
+
+    The branching structure of the call tree is managed uniformly through the `children` interface
+    of SfgCallTreeNode. Each subclass must ensure that access to and modification of
+    the branching structure through the `children` property and the `child` and `set_child`
+    methods is possible, if necessary by overriding the property and methods.
+    """
+
+    @property
+    @abstractmethod
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        """This node's children"""
+
+    @abstractmethod
+    def get_code(self, ctx: SfgContext) -> str:
+        """Returns the code of this node.
+
+        By convention, the code block emitted by this function should not contain a trailing newline.
+        """
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        """Return a set of header includes required by this node"""
+        return set()
+
+
+class SfgCallTreeLeaf(SfgCallTreeNode, ABC):
+    """A leaf node of the call tree.
+
+    Leaf nodes must implement `required_parameters` for automatic parameter collection.
+    """
+
+    def __init__(self):
+        super().__init__()
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        return ()
+
+    @property
+    @abstractmethod
+    def depends(self) -> set[SfgVar]:
+        """Set of objects this leaf depends on"""
+
+
+class SfgEmptyNode(SfgCallTreeLeaf):
+    """A leaf node that does not emit any code.
+
+    Empty nodes must still implement `required_parameters`.
+    """
+
+    def __init__(self):
+        super().__init__()
+
+    def get_code(self, ctx: SfgContext) -> str:
+        return ""
+
+
+class SfgStatements(SfgCallTreeLeaf):
+    """Represents (a sequence of) statements in the source language.
+
+    This class groups together arbitrary code strings
+    (e.g. sequences of C++ statements, cf. https://en.cppreference.com/w/cpp/language/statements),
+    and annotates them with the set of symbols read and written by these statements.
+
+    It is the user's responsibility to ensure that the code string is valid code in the output language,
+    and that the lists of required and defined objects are correct and complete.
+
+    Args:
+        code_string: Code to be printed out.
+        defined_params: Variables that will be newly defined and visible to code in sequence after these statements.
+        required_params: Variables that are required as input to these statements.
+    """
+
+    def __init__(
+        self,
+        code_string: str,
+        defines: Iterable[SfgVar],
+        depends: Iterable[SfgVar],
+    ):
+        super().__init__()
+
+        self._code_string = code_string
+
+        self._defines = set(defines)
+        self._depends = set(depends)
+
+        self._required_includes = set()
+        for obj in chain(depends, defines):
+            if isinstance(obj, SfgVar):
+                self._required_includes |= obj.required_includes
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return self._depends
+
+    @property
+    def defines(self) -> set[SfgVar]:
+        return self._defines
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return self._required_includes
+
+    def get_code(self, ctx: SfgContext) -> str:
+        return self._code_string
+
+
+class SfgFunctionParams(SfgEmptyNode):
+    def __init__(self, parameters: Sequence[SfgVar]):
+        super().__init__()
+        self._params = set(parameters)
+
+        self._required_includes = set()
+        for obj in parameters:
+            if isinstance(obj, SfgVar):
+                self._required_includes |= obj.required_includes
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return self._params
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return self._required_includes
+
+
+class SfgRequireIncludes(SfgEmptyNode):
+    def __init__(self, includes: Sequence[SfgHeaderInclude]):
+        super().__init__()
+        self._required_includes = set(includes)
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return set()
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return self._required_includes
+
+
+class SfgSequence(SfgCallTreeNode):
+    __match_args__ = ("children",)
+
+    def __init__(self, children: Sequence[SfgCallTreeNode]):
+        self._children = list(children)
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        return self._children
+
+    @children.setter
+    def children(self, cs: Sequence[SfgCallTreeNode]):
+        self._children = list(cs)
+
+    def __getitem__(self, idx: int) -> SfgCallTreeNode:
+        return self._children[idx]
+
+    def __setitem__(self, idx: int, c: SfgCallTreeNode):
+        self._children[idx] = c
+
+    def get_code(self, ctx: SfgContext) -> str:
+        return "\n".join(c.get_code(ctx) for c in self._children)
+
+
+class SfgBlock(SfgCallTreeNode):
+    def __init__(self, seq: SfgSequence):
+        self._seq = seq
+
+    @property
+    def sequence(self) -> SfgSequence:
+        return self._seq
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        return (self._seq,)
+
+    def get_code(self, ctx: SfgContext) -> str:
+        seq_code = ctx.codestyle.indent(self._seq.get_code(ctx))
+
+        return "{\n" + seq_code + "\n}"
+
+
+# class SfgForLoop(SfgCallTreeNode):
+#     def __init__(self, control_line: SfgStatements, body: SfgCallTreeNode):
+#         super().__init__(control_line, body)
+
+#     @property
+#     def body(self) -> SfgStatements:
+#         return cast(SfgStatements)
+
+
+class SfgKernelCallNode(SfgCallTreeLeaf):
+    def __init__(self, kernel_handle: SfgKernelHandle):
+        super().__init__()
+        self._kernel_handle = kernel_handle
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return set(self._kernel_handle.parameters)
+
+    def get_code(self, ctx: SfgContext) -> str:
+        ast_params = self._kernel_handle.parameters
+        fnc_name = self._kernel_handle.fully_qualified_name
+        call_parameters = ", ".join([p.name for p in ast_params])
+
+        return f"{fnc_name}({call_parameters});"
+
+
+class SfgCudaKernelInvocation(SfgCallTreeLeaf):
+    def __init__(
+        self,
+        kernel_handle: SfgKernelHandle,
+        num_blocks_code: str,
+        threads_per_block_code: str,
+        stream_code: str | None,
+        depends: set[SfgVar],
+    ):
+        from pystencils import Target
+        from pystencils.backend.kernelfunction import GpuKernelFunction
+
+        func = kernel_handle.get_kernel_function()
+        if not (isinstance(func, GpuKernelFunction) and func.target == Target.CUDA):
+            raise ValueError(
+                "An `SfgCudaKernelInvocation` node can only call a CUDA kernel."
+            )
+
+        super().__init__()
+        self._kernel_handle = kernel_handle
+        self._num_blocks = num_blocks_code
+        self._threads_per_block = threads_per_block_code
+        self._stream = stream_code
+        self._depends = depends
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return set(self._kernel_handle.parameters) | self._depends
+
+    def get_code(self, ctx: SfgContext) -> str:
+        ast_params = self._kernel_handle.parameters
+        fnc_name = self._kernel_handle.fully_qualified_name
+        call_parameters = ", ".join([p.name for p in ast_params])
+
+        grid_args = [self._num_blocks, self._threads_per_block]
+        if self._stream is not None:
+            grid_args += [self._stream]
+
+        grid = "<<< " + ", ".join(grid_args) + " >>>"
+        return f"{fnc_name}{grid}({call_parameters});"
+
+
+class SfgBranch(SfgCallTreeNode):
+    def __init__(
+        self,
+        cond: SfgStatements,
+        branch_true: SfgSequence,
+        branch_false: SfgSequence | None = None,
+    ):
+        self._cond = cond
+        self._branch_true = branch_true
+        self._branch_false = branch_false
+
+    @property
+    def condition(self) -> SfgStatements:
+        return self._cond
+
+    @property
+    def branch_true(self) -> SfgSequence:
+        return self._branch_true
+
+    @property
+    def branch_false(self) -> SfgSequence | None:
+        return self._branch_false
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        return (
+            self._cond,
+            self._branch_true,
+        ) + ((self.branch_false,) if self.branch_false is not None else ())
+
+    def get_code(self, ctx: SfgContext) -> str:
+        code = f"if({self.condition.get_code(ctx)}) {{\n"
+        code += ctx.codestyle.indent(self.branch_true.get_code(ctx))
+        code += "\n}"
+
+        if self.branch_false is not None:
+            code += "else {\n"
+            code += ctx.codestyle.indent(self.branch_false.get_code(ctx))
+            code += "\n}"
+
+        return code
+
+
+class SfgSwitchCase(SfgCallTreeNode):
+    DefaultCaseType = NewType("DefaultCaseType", object)
+    Default = DefaultCaseType(object())
+
+    def __init__(self, label: str | SfgSwitchCase.DefaultCaseType, body: SfgSequence):
+        self._label = label
+        self._body = body
+
+    @property
+    def label(self) -> str | DefaultCaseType:
+        return self._label
+
+    @property
+    def body(self) -> SfgSequence:
+        return self._body
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        return (self._body,)
+
+    @property
+    def is_default(self) -> bool:
+        return self._label == SfgSwitchCase.Default
+
+    def get_code(self, ctx: SfgContext) -> str:
+        code = ""
+        if self._label == SfgSwitchCase.Default:
+            code += "default: {\n"
+        else:
+            code += f"case {self._label}: {{\n"
+        code += ctx.codestyle.indent(self.body.get_code(ctx))
+        code += "\nbreak;\n}"
+        return code
+
+
+class SfgSwitch(SfgCallTreeNode):
+    def __init__(
+        self,
+        switch_arg: SfgStatements,
+        cases_dict: dict[str, SfgSequence],
+        default: SfgSequence | None = None,
+    ):
+        self._cases = [SfgSwitchCase(label, body) for label, body in cases_dict.items()]
+        if default is not None:
+            # invariant: the default case is always the last child
+            self._cases += [SfgSwitchCase(SfgSwitchCase.Default, default)]
+        self._switch_arg = switch_arg
+        self._default = (
+            SfgSwitchCase(SfgSwitchCase.Default, default)
+            if default is not None
+            else None
+        )
+
+    @property
+    def switch_arg(self) -> str | SfgStatements:
+        return self._switch_arg
+
+    @property
+    def default(self) -> SfgCallTreeNode | None:
+        return self._default
+
+    @property
+    def children(self) -> tuple[SfgCallTreeNode, ...]:
+        return (self._switch_arg,) + tuple(self._cases)
+
+    @property
+    def cases(self) -> tuple[SfgCallTreeNode, ...]:
+        if self._default is not None:
+            return tuple(self._cases[:-1])
+        else:
+            return tuple(self._cases)
+
+    @cases.setter
+    def cases(self, cs: Sequence[SfgSwitchCase]) -> None:
+        if len(cs) != len(self._cases):
+            raise ValueError("The number of child nodes must remain the same!")
+
+        self._default = None
+        for i, c in enumerate(cs):
+            if c.is_default:
+                if i != len(cs) - 1:
+                    raise ValueError("Default case must be listed last.")
+                else:
+                    self._default = c
+
+        self._children = list(cs)
+
+    def set_case(self, idx: int, c: SfgSwitchCase):
+        if c.is_default:
+            if idx != len(self._children) - 1:
+                raise ValueError("Default case must be the last child.")
+            elif self._default is None:
+                raise ValueError("Cannot replace normal case with default case.")
+            else:
+                self._default = c
+                self._children[-1] = c
+        else:
+            self._children[idx] = c
+
+    def get_code(self, ctx: SfgContext) -> str:
+        code = f"switch({self._switch_arg.get_code(ctx)}) {{\n"
+        code += "\n".join(c.get_code(ctx) for c in self.children)
+        code += "}"
+        return code
diff --git a/src/pystencilssfg/ir/postprocessing.py b/src/pystencilssfg/ir/postprocessing.py
new file mode 100644
index 0000000000000000000000000000000000000000..c30910d59f6c6626dd6fc147f93491e9b175dace
--- /dev/null
+++ b/src/pystencilssfg/ir/postprocessing.py
@@ -0,0 +1,303 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING, Sequence
+import warnings
+from functools import reduce
+from dataclasses import dataclass
+
+from abc import ABC, abstractmethod
+
+import sympy as sp
+
+from pystencils import Field, TypedSymbol
+from pystencils.backend.kernelfunction import (
+    FieldPointerParam,
+    FieldShapeParam,
+    FieldStrideParam,
+)
+
+from ..exceptions import SfgException
+
+from .call_tree import SfgCallTreeNode, SfgCallTreeLeaf, SfgSequence, SfgStatements
+from ..ir.source_components import SfgVar, SfgSymbolLike
+from ..lang import IFieldExtraction, SrcField, SrcVector
+
+if TYPE_CHECKING:
+    from ..context import SfgContext
+    from .source_components import SfgClass
+
+
+class FlattenSequences:
+    """Flattens any nested sequences occuring in a kernel call tree."""
+
+    def __call__(self, node: SfgCallTreeNode) -> None:
+        self.visit(node)
+
+    def visit(self, node: SfgCallTreeNode):
+        match node:
+            case SfgSequence():
+                self.flatten(node)
+            case _:
+                for c in node.children:
+                    self.visit(c)
+
+    def flatten(self, sequence: SfgSequence) -> None:
+        children_flattened: list[SfgCallTreeNode] = []
+
+        def flatten(seq: SfgSequence):
+            for c in seq.children:
+                if isinstance(c, SfgSequence):
+                    flatten(c)
+                else:
+                    children_flattened.append(c)
+
+        flatten(sequence)
+
+        for c in children_flattened:
+            self.visit(c)
+
+        sequence.children = children_flattened
+
+
+class PostProcessingContext:
+    def __init__(self, enclosing_class: SfgClass | None = None) -> None:
+        self.enclosing_class: SfgClass | None = enclosing_class
+        self.live_objects: set[SfgVar] = set()
+
+    def is_method(self) -> bool:
+        return self.enclosing_class is not None
+
+    def get_enclosing_class(self) -> SfgClass:
+        if self.enclosing_class is None:
+            raise SfgException("Cannot get the enclosing class of a free function.")
+
+        return self.enclosing_class
+
+
+@dataclass(frozen=True)
+class PostProcessingResult:
+    function_params: set[SfgVar]
+
+
+class CallTreePostProcessing:
+    def __init__(self, enclosing_class: SfgClass | None = None):
+        self._enclosing_class = enclosing_class
+        self._flattener = FlattenSequences()
+
+    def __call__(self, ast: SfgCallTreeNode) -> PostProcessingResult:
+        params = self.get_live_objects(ast)
+        params_by_name: dict[str, SfgVar] = dict()
+
+        for param in params:
+            if param.name in params_by_name:
+                other = params_by_name[param.name]
+
+                if param.dtype == other.dtype:
+                    warnings.warn(
+                        "Encountered two non-identical parameters with same name and data type:\n"
+                        f"    {repr(param)}\n"
+                        "and\n"
+                        f"    {repr(other)}\n"
+                    )
+                else:
+                    raise SfgException(
+                        "Encountered two parameters with same name but different data types:\n"
+                        f"    {repr(param)}\n"
+                        "and\n"
+                        f"    {repr(other)}"
+                    )
+            params_by_name[param.name] = param
+
+        return PostProcessingResult(set(params_by_name.values()))
+
+    def handle_sequence(self, seq: SfgSequence, ppc: PostProcessingContext):
+        def iter_nested_sequences(seq: SfgSequence):
+            for i in range(len(seq.children) - 1, -1, -1):
+                c = seq.children[i]
+
+                if isinstance(c, SfgDeferredNode):
+                    c = c.expand(ppc)
+                    seq[i] = c
+
+                if isinstance(c, SfgSequence):
+                    iter_nested_sequences(c)
+                else:
+                    if isinstance(c, SfgStatements):
+                        ppc.live_objects -= c.defines
+
+                    ppc.live_objects |= self.get_live_objects(c)
+
+        iter_nested_sequences(seq)
+
+    def get_live_objects(self, node: SfgCallTreeNode) -> set[SfgVar]:
+        match node:
+            case SfgSequence():
+                ppc = self._ppc()
+                self.handle_sequence(node, ppc)
+                return ppc.live_objects
+
+            case SfgCallTreeLeaf():
+                return node.depends
+
+            case SfgDeferredNode():
+                raise SfgException("Deferred nodes can only occur inside a sequence.")
+
+            case _:
+                return reduce(
+                    lambda x, y: x | y,
+                    (self.get_live_objects(c) for c in node.children),
+                    set(),
+                )
+
+    def _ppc(self) -> PostProcessingContext:
+        return PostProcessingContext(enclosing_class=self._enclosing_class)
+
+
+class SfgDeferredNode(SfgCallTreeNode, ABC):
+    """Nodes of this type are inserted as placeholders into the kernel call tree
+    and need to be expanded at a later time.
+
+    Subclasses of SfgDeferredNode correspond to nodes that cannot be created yet
+    because information required for their construction is not yet known.
+    """
+
+    @property
+    def children(self) -> Sequence[SfgCallTreeNode]:
+        raise SfgException(
+            "Invalid access into deferred node; deferred nodes must be expanded first."
+        )
+
+    @abstractmethod
+    def expand(self, ppc: PostProcessingContext) -> SfgCallTreeNode:
+        pass
+
+    def get_code(self, ctx: SfgContext) -> str:
+        raise SfgException(
+            "Invalid access into deferred node; deferred nodes must be expanded first."
+        )
+
+
+class SfgDeferredParamMapping(SfgDeferredNode):
+    def __init__(self, lhs: SfgVar, rhs: set[SfgVar], mapping: str):
+        self._lhs = lhs
+        self._rhs = rhs
+        self._mapping = mapping
+
+    def expand(self, ppc: PostProcessingContext) -> SfgCallTreeNode:
+        if self._lhs in ppc.live_objects:
+            return SfgStatements(self._mapping, (self._lhs,), tuple(self._rhs))
+        else:
+            return SfgSequence([])
+
+
+class SfgDeferredFieldMapping(SfgDeferredNode):
+    def __init__(self, psfield: Field, extraction: IFieldExtraction | SrcField):
+        self._field = psfield
+        self._extraction: IFieldExtraction = (
+            extraction
+            if isinstance(extraction, IFieldExtraction)
+            else extraction.get_extraction()
+        )
+
+    # type: ignore
+    def expand(self, ppc: PostProcessingContext) -> SfgCallTreeNode:
+        #    Find field pointer
+        ptr: SfgSymbolLike[FieldPointerParam] | None = None
+        shape: list[SfgSymbolLike[FieldShapeParam] | int | None] = [None] * len(
+            self._field.shape
+        )
+        strides: list[SfgSymbolLike[FieldStrideParam] | int | None] = [None] * len(
+            self._field.strides
+        )
+
+        for param in ppc.live_objects:
+            #   idk why, but mypy does not understand these pattern matches
+            match param:
+                case SfgSymbolLike(FieldPointerParam(_, _, field)) if field == self._field:  # type: ignore
+                    ptr = param
+                case SfgSymbolLike(
+                    FieldShapeParam(_, _, field, coord)  # type: ignore
+                ) if field == self._field:  # type: ignore
+                    shape[coord] = param  # type: ignore
+                case SfgSymbolLike(
+                    FieldStrideParam(_, _, field, coord)  # type: ignore
+                ) if field == self._field:  # type: ignore
+                    strides[coord] = param  # type: ignore
+
+        #   Find constant sizes
+        for coord, s in enumerate(self._field.shape):
+            if not isinstance(s, TypedSymbol):
+                shape[coord] = s
+
+        #   Find constant strides
+        for coord, s in enumerate(self._field.strides):
+            if not isinstance(s, TypedSymbol):
+                strides[coord] = s
+
+        #   Now we have all the symbols, start extracting them
+        nodes = []
+
+        if ptr is not None:
+            expr = self._extraction.ptr()
+            nodes.append(
+                SfgStatements(
+                    f"{ptr.dtype} {ptr.name} {{ {expr} }};", (ptr,), expr.depends
+                )
+            )
+
+        def get_shape(coord, symb: SfgSymbolLike | int):
+            expr = self._extraction.size(coord)
+
+            if expr is None:
+                raise SfgException(
+                    f"Cannot extract shape in coordinate {coord} from {self._extraction}"
+                )
+
+            if isinstance(symb, SfgSymbolLike):
+                return SfgStatements(
+                    f"{symb.dtype} {symb.name} {{ {expr} }};", (symb,), expr.depends
+                )
+            else:
+                return SfgStatements(f"/* {expr} == {symb} */", (), ())
+
+        def get_stride(coord, symb: SfgSymbolLike | int):
+            expr = self._extraction.stride(coord)
+
+            if expr is None:
+                raise SfgException(
+                    f"Cannot extract stride in coordinate {coord} from {self._extraction}"
+                )
+
+            if isinstance(symb, SfgSymbolLike):
+                return SfgStatements(
+                    f"{symb.dtype} {symb.name} {{ {expr} }};", (symb,), expr.depends
+                )
+            else:
+                return SfgStatements(f"/* {expr} == {symb} */", (), ())
+
+        nodes += [get_shape(c, s) for c, s in enumerate(shape) if s is not None]
+        nodes += [get_stride(c, s) for c, s in enumerate(strides) if s is not None]
+
+        return SfgSequence(nodes)
+
+
+class SfgDeferredVectorMapping(SfgDeferredNode):
+    def __init__(self, scalars: Sequence[sp.Symbol | SfgVar], vector: SrcVector):
+        self._scalars = {sc.name: (i, sc) for i, sc in enumerate(scalars)}
+        self._vector = vector
+
+    def expand(self, ppc: PostProcessingContext) -> SfgCallTreeNode:
+        nodes = []
+
+        for param in ppc.live_objects:
+            if param.name in self._scalars:
+                idx, _ = self._scalars[param.name]
+                expr = self._vector.extract_component(idx)
+                nodes.append(
+                    SfgStatements(
+                        f"{param.dtype} {param.name} {{ {expr} }};",
+                        (param,),
+                        expr.depends,
+                    )
+                )
+
+        return SfgSequence(nodes)
diff --git a/src/pystencilssfg/source_components.py b/src/pystencilssfg/ir/source_components.py
similarity index 71%
rename from src/pystencilssfg/source_components.py
rename to src/pystencilssfg/ir/source_components.py
index 43d58a069712f7465278b548622079f311cfcff2..6aace6991d8ef0931e963ede1849d5df0a6b350a 100644
--- a/src/pystencilssfg/source_components.py
+++ b/src/pystencilssfg/ir/source_components.py
@@ -2,20 +2,23 @@ from __future__ import annotations
 
 from abc import ABC
 from enum import Enum, auto
-from typing import TYPE_CHECKING, Sequence, Generator
+from typing import TYPE_CHECKING, Sequence, Generator, TypeVar, Generic, Any
 from dataclasses import replace
 from itertools import chain
 
-from pystencils import CreateKernelConfig, create_kernel
-from pystencils.astnodes import KernelFunction
+from pystencils import CreateKernelConfig, create_kernel, Field
+from pystencils.backend.kernelfunction import (
+    KernelFunction,
+    KernelParameter,
+    FieldParameter,
+)
+from pystencils.types import PsType, PsCustomType
 
-from .types import SrcType
-from .source_concepts import SrcObject
-from .exceptions import SfgException
+from ..exceptions import SfgException
 
 if TYPE_CHECKING:
-    from .tree import SfgCallTreeNode
-    from .context import SfgContext
+    from . import SfgCallTreeNode
+    from ..context import SfgContext
 
 
 class SfgEmptyLines:
@@ -28,6 +31,19 @@ class SfgEmptyLines:
 
 
 class SfgHeaderInclude:
+
+    @staticmethod
+    def parse(incl: str | SfgHeaderInclude, private: bool = False):
+        if isinstance(incl, SfgHeaderInclude):
+            return incl
+
+        system_header = False
+        if incl.startswith("<") and incl.endswith(">"):
+            incl = incl[1:-1]
+            system_header = True
+
+        return SfgHeaderInclude(incl, system_header=system_header, private=private)
+
     def __init__(
         self, header_file: str, system_header: bool = False, private: bool = False
     ):
@@ -60,38 +76,51 @@ class SfgHeaderInclude:
 
 
 class SfgKernelNamespace:
-    def __init__(self, ctx, name: str):
+    """A namespace grouping together a number of kernels."""
+
+    def __init__(self, ctx: SfgContext, name: str):
         self._ctx = ctx
         self._name = name
-        self._asts: dict[str, KernelFunction] = dict()
+        self._kernel_functions: dict[str, KernelFunction] = dict()
 
     @property
     def name(self):
         return self._name
 
     @property
-    def asts(self):
-        yield from self._asts.values()
+    def kernel_functions(self):
+        yield from self._kernel_functions.values()
+
+    def get_kernel_function(self, khandle: SfgKernelHandle) -> KernelFunction:
+        if khandle.kernel_namespace is not self:
+            raise ValueError(
+                f"Kernel handle does not belong to this namespace: {khandle}"
+            )
 
-    def add(self, ast: KernelFunction, name: str | None = None):
+        return self._kernel_functions[khandle.kernel_name]
+
+    def add(self, kernel: KernelFunction, name: str | None = None):
         """Adds an existing pystencils AST to this namespace.
         If a name is specified, the AST's function name is changed."""
         if name is not None:
             astname = name
         else:
-            astname = ast.function_name
+            astname = kernel.name
 
-        if astname in self._asts:
+        if astname in self._kernel_functions:
             raise ValueError(
                 f"Duplicate ASTs: An AST with name {astname} already exists in namespace {self._name}"
             )
 
         if name is not None:
-            ast.function_name = name
+            kernel.name = name
+
+        self._kernel_functions[astname] = kernel
 
-        self._asts[astname] = ast
+        for header in kernel.required_headers:
+            self._ctx.add_include(SfgHeaderInclude.parse(header, private=True))
 
-        return SfgKernelHandle(self._ctx, astname, self, ast.get_parameters())
+        return SfgKernelHandle(self._ctx, astname, self, kernel.parameters)
 
     def create(
         self,
@@ -100,18 +129,14 @@ class SfgKernelNamespace:
         config: CreateKernelConfig | None = None,
     ):
         """Creates a new pystencils kernel from a list of assignments and a configuration.
-        This is a wrapper around
-        [`pystencils.create_kernel`](
-            https://pycodegen.pages.i10git.cs.fau.de/pystencils/
-            sphinx/kernel_compile_and_call.html#pystencils.create_kernel
-        )
-        with a subsequent call to [`add`][pystencilssfg.source_components.SfgKernelNamespace.add].
+        This is a wrapper around `pystencils.create_kernel`
+        with a subsequent call to `add`.
         """
         if config is None:
             config = CreateKernelConfig()
 
         if name is not None:
-            if name in self._asts:
+            if name in self._kernel_functions:
                 raise ValueError(
                     f"Duplicate ASTs: An AST with name {name} already exists in namespace {self._name}"
                 )
@@ -123,26 +148,28 @@ class SfgKernelNamespace:
 
 
 class SfgKernelHandle:
+    """A handle that represents a pystencils kernel within a kernel namespace."""
+
     def __init__(
         self,
         ctx: SfgContext,
         name: str,
         namespace: SfgKernelNamespace,
-        parameters: Sequence[KernelFunction.Parameter],
+        parameters: Sequence[KernelParameter],
     ):
         self._ctx = ctx
         self._name = name
         self._namespace = namespace
-        self._parameters = parameters
+        self._parameters = [SfgSymbolLike(p) for p in parameters]
 
-        self._scalar_params = set()
-        self._fields = set()
+        self._scalar_params: set[SfgSymbolLike] = set()
+        self._fields: set[Field] = set()
 
         for param in self._parameters:
-            if param.is_field_parameter:
-                self._fields |= set(param.fields)
+            if isinstance(param.wrapped, FieldParameter):
+                self._fields.add(param.wrapped.field)
             else:
-                self._scalar_params.add(param.symbol)
+                self._scalar_params.add(param)
 
     @property
     def kernel_name(self):
@@ -161,7 +188,7 @@ class SfgKernelHandle:
                 return f"{fqn}::{self.kernel_namespace.name}::{self.kernel_name}"
 
     @property
-    def parameters(self):
+    def parameters(self) -> Sequence[SfgSymbolLike]:
         return self._parameters
 
     @property
@@ -172,34 +199,112 @@ class SfgKernelHandle:
     def fields(self):
         return self.fields
 
+    def get_kernel_function(self) -> KernelFunction:
+        return self._namespace.get_kernel_function(self)
+
+
+class SfgVar:
+    __match_args__ = ("name", "dtype")
+
+    def __init__(
+        self,
+        name: str,
+        dtype: PsType,
+        required_includes: set[SfgHeaderInclude] | None = None,
+    ):
+        self._name = name
+        self._dtype = dtype
+
+        self._required_includes = (
+            required_includes if required_includes is not None else set()
+        )
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def dtype(self) -> PsType:
+        return self._dtype
+
+    def _args(self) -> tuple[Any, ...]:
+        return (self._name, self._dtype)
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, SfgVar):
+            return False
+
+        return self._args() == other._args()
+
+    def __hash__(self) -> int:
+        return hash(self._args())
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return self._required_includes
+
+    def __str__(self) -> str:
+        return self._name
+
+    def __repr__(self) -> str:
+        return f"SfgVar( {self._name}, {repr(self._dtype)} )"
+
+
+SymbolLike_T = TypeVar("SymbolLike_T", bound=KernelParameter)
+
+
+class SfgSymbolLike(SfgVar, Generic[SymbolLike_T]):
+    __match_args__ = ("wrapped",)
+
+    """Cast pystencils- or SymPy-native symbol-like objects as a `SfgVar`."""
+
+    def __init__(self, param: SymbolLike_T):
+        self._param = param
+        super().__init__(param.name, param.dtype)
+
+    @property
+    def wrapped(self) -> SymbolLike_T:
+        return self._param
+
+    def _args(self):
+        return (self._param,)
+
 
 class SfgFunction:
+    __match_args__ = ("name", "tree", "parameters")
+
     def __init__(
-        self, name: str, tree: SfgCallTreeNode, return_type: SrcType = SrcType("void")
+        self,
+        name: str,
+        tree: SfgCallTreeNode,
+        return_type: PsType = PsCustomType("void"),
+        _is_method: bool = False,
     ):
         self._name = name
         self._tree = tree
         self._return_type = return_type
 
-        from .visitors.tree_visitors import ExpandingParameterCollector
+        self._parameters: set[SfgVar]
+        if not _is_method:
+            from .postprocessing import CallTreePostProcessing
 
-        param_collector = ExpandingParameterCollector()
-        self._parameters = param_collector.visit(self._tree)
+            param_collector = CallTreePostProcessing()
+            self._parameters = param_collector(self._tree).function_params
 
     @property
-    def name(self):
+    def name(self) -> str:
         return self._name
 
     @property
-    def parameters(self):
+    def parameters(self) -> set[SfgVar]:
         return self._parameters
 
     @property
-    def tree(self):
+    def tree(self) -> SfgCallTreeNode:
         return self._tree
 
     @property
-    def return_type(self) -> SrcType:
+    def return_type(self) -> PsType:
         return self._return_type
 
 
@@ -310,9 +415,9 @@ class SfgInClassDefinition(SfgClassMember):
         return self._text
 
 
-class SfgMemberVariable(SrcObject, SfgClassMember):
-    def __init__(self, name: str, dtype: SrcType):
-        SrcObject.__init__(self, name, dtype)
+class SfgMemberVariable(SfgVar, SfgClassMember):
+    def __init__(self, name: str, dtype: PsType):
+        SfgVar.__init__(self, name, dtype)
         SfgClassMember.__init__(self)
 
 
@@ -321,16 +426,21 @@ class SfgMethod(SfgFunction, SfgClassMember):
         self,
         name: str,
         tree: SfgCallTreeNode,
-        return_type: SrcType = SrcType("void"),
+        return_type: PsType = PsCustomType("void"),
         inline: bool = False,
         const: bool = False,
     ):
-        SfgFunction.__init__(self, name, tree, return_type=return_type)
+        SfgFunction.__init__(self, name, tree, return_type=return_type, _is_method=True)
         SfgClassMember.__init__(self)
 
         self._inline = inline
         self._const = const
 
+        from .postprocessing import CallTreePostProcessing
+
+        param_collector = CallTreePostProcessing()
+        self._parameters: set[SfgVar] = param_collector(self._tree).function_params
+
     @property
     def inline(self) -> bool:
         return self._inline
@@ -343,7 +453,7 @@ class SfgMethod(SfgFunction, SfgClassMember):
 class SfgConstructor(SfgClassMember):
     def __init__(
         self,
-        parameters: Sequence[SrcObject] = (),
+        parameters: Sequence[SfgVar] = (),
         initializers: Sequence[str] = (),
         body: str = "",
     ):
@@ -353,7 +463,7 @@ class SfgConstructor(SfgClassMember):
         self._body = body
 
     @property
-    def parameters(self) -> tuple[SrcObject, ...]:
+    def parameters(self) -> tuple[SfgVar, ...]:
         return self._parameters
 
     @property
@@ -382,6 +492,8 @@ class SfgClass:
     [SfgClassComposer][pystencilssfg.composer.SfgClassComposer].
     """
 
+    __match_args__ = ("class_name",)
+
     def __init__(
         self,
         class_name: str,
@@ -409,8 +521,8 @@ class SfgClass:
         return self._class_name
 
     @property
-    def src_type(self) -> SrcType:
-        return SrcType(self._class_name)
+    def src_type(self) -> PsType:
+        return PsCustomType(self._class_name)
 
     @property
     def base_classes(self) -> tuple[str, ...]:
diff --git a/src/pystencilssfg/lang/__init__.py b/src/pystencilssfg/lang/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..543d3094131f7dd4d0d8edf1673c2e5a27597065
--- /dev/null
+++ b/src/pystencilssfg/lang/__init__.py
@@ -0,0 +1,15 @@
+from .expressions import (
+    DependentExpression,
+    AugExpr,
+    IFieldExtraction,
+    SrcField,
+    SrcVector,
+)
+
+__all__ = [
+    "DependentExpression",
+    "AugExpr",
+    "IFieldExtraction",
+    "SrcField",
+    "SrcVector",
+]
diff --git a/src/pystencilssfg/source_concepts/cpp/__init__.py b/src/pystencilssfg/lang/cpp/__init__.py
similarity index 77%
rename from src/pystencilssfg/source_concepts/cpp/__init__.py
rename to src/pystencilssfg/lang/cpp/__init__.py
index 7a2a9106115caa3bcb0b4b99659db065b0494b7c..a00c2c2b3ea1c279f04ef6d460320b97aad60111 100644
--- a/src/pystencilssfg/source_concepts/cpp/__init__.py
+++ b/src/pystencilssfg/lang/cpp/__init__.py
@@ -1,6 +1,7 @@
 from .std_mdspan import StdMdspan, mdspan_ref
 from .std_vector import StdVector, std_vector_ref
 from .std_tuple import StdTuple, std_tuple_ref
+from .std_span import StdSpan, std_span_ref
 
 __all__ = [
     "StdMdspan",
@@ -9,4 +10,6 @@ __all__ = [
     "std_vector_ref",
     "StdTuple",
     "std_tuple_ref",
+    "StdSpan",
+    "std_span_ref",
 ]
diff --git a/src/pystencilssfg/lang/cpp/std.py b/src/pystencilssfg/lang/cpp/std.py
new file mode 100644
index 0000000000000000000000000000000000000000..48cd249496b182eb911f1264a8e1a5410aa141e3
--- /dev/null
+++ b/src/pystencilssfg/lang/cpp/std.py
@@ -0,0 +1,12 @@
+from .std_span import std_span_ref
+from .std_mdspan import mdspan_ref
+from .std_vector import std_vector_ref
+
+span = std_span_ref
+"""Create an ``std::span`` reference for a 1D pystencils field"""
+
+mdspan = mdspan_ref
+"""Create an ``std::mdspan`` reference for a pystencils field"""
+
+vector = std_vector_ref
+"""Create an ``std::vector`` reference for a 1D pystencils field"""
diff --git a/src/pystencilssfg/lang/cpp/std_mdspan.py b/src/pystencilssfg/lang/cpp/std_mdspan.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b460fd13e7a924f17a3745bf230add517be53c1
--- /dev/null
+++ b/src/pystencilssfg/lang/cpp/std_mdspan.py
@@ -0,0 +1,92 @@
+from typing import cast
+from sympy import Symbol
+
+from pystencils import Field
+from pystencils.types import (
+    PsType,
+    PsCustomType,
+    PsSignedIntegerType,
+    PsUnsignedIntegerType,
+)
+
+from pystencilssfg.lang.expressions import AugExpr
+
+from ...lang import SrcField, IFieldExtraction
+from ...ir.source_components import SfgHeaderInclude
+
+
+class StdMdspan(SrcField):
+    dynamic_extent = "std::dynamic_extent"
+
+    def __init__(
+        self,
+        T: PsType,
+        extents: tuple[int | str, ...],
+        extents_type: PsType = PsSignedIntegerType(64),
+        reference: bool = False,
+    ):
+        cpp_typestr = T.c_string()
+        extents_type_str = extents_type.c_string()
+
+        extents_str = (
+            f"std::extents< {extents_type_str}, {', '.join(str(e) for e in extents)} >"
+        )
+        typestring = (
+            f"std::mdspan< {cpp_typestr}, {extents_str} > {'&' if reference else ''}"
+        )
+        super().__init__(PsCustomType(typestring))
+
+        self._extents = extents
+        self._dim = len(extents)
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return {SfgHeaderInclude("experimental/mdspan", system_header=True)}
+
+    def get_extraction(self) -> IFieldExtraction:
+        mdspan = self
+
+        class Extraction(IFieldExtraction):
+            def ptr(self) -> AugExpr:
+                return AugExpr.format("{}.data_handle()", mdspan)
+
+            def size(self, coordinate: int) -> AugExpr | None:
+                if coordinate > mdspan._dim:
+                    return None
+                else:
+                    return AugExpr.format("{}.extents().extent({})", mdspan, coordinate)
+
+            def stride(self, coordinate: int) -> AugExpr | None:
+                if coordinate > mdspan._dim:
+                    return None
+                else:
+                    return AugExpr.format("{}.stride({})", mdspan, coordinate)
+
+        return Extraction()
+
+
+def mdspan_ref(field: Field, extents_type: PsType = PsUnsignedIntegerType(64)):
+    """Creates a `std::mdspan &` for a given pystencils field."""
+    from pystencils.field import layout_string_to_tuple
+
+    if field.layout != layout_string_to_tuple("soa", field.spatial_dimensions):
+        raise NotImplementedError(
+            "mdspan mapping is currently only available for structure-of-arrays fields"
+        )
+
+    extents: list[str | int] = []
+
+    for s in field.spatial_shape:
+        extents.append(
+            StdMdspan.dynamic_extent if isinstance(s, Symbol) else cast(int, s)
+        )
+
+    for s in field.index_shape:
+        extents.append(StdMdspan.dynamic_extent if isinstance(s, Symbol) else s)
+
+    return StdMdspan(
+        field.dtype,
+        tuple(extents),
+        extents_type=extents_type,
+        reference=True,
+    ).var(field.name)
diff --git a/src/pystencilssfg/lang/cpp/std_span.py b/src/pystencilssfg/lang/cpp/std_span.py
new file mode 100644
index 0000000000000000000000000000000000000000..37ee04236cdde70a04e7155c2d0f06487b385634
--- /dev/null
+++ b/src/pystencilssfg/lang/cpp/std_span.py
@@ -0,0 +1,50 @@
+from typing import Union
+
+from pystencils.field import Field
+from pystencils.types import PsType, PsCustomType
+
+from ...lang import SrcField, IFieldExtraction, AugExpr
+from ...ir.source_components import SfgHeaderInclude
+from ...exceptions import SfgException
+
+
+class StdSpan(SrcField):
+    def __init__(self, T: Union[PsCustomType, PsType], ref=True, const=False):
+        src_type = f"{'const ' if const else ''}std::span< {T.c_string()} > {'&' if ref else ''}"
+        self._element_type = T
+        super().__init__(PsCustomType(src_type))
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return {
+            SfgHeaderInclude("span", system_header=True),
+        }
+
+    def get_extraction(self) -> IFieldExtraction:
+        span = self
+
+        class Extraction(IFieldExtraction):
+            def ptr(self) -> AugExpr:
+                return AugExpr.format("{}.data()", span)
+
+            def size(self, coordinate: int) -> AugExpr | None:
+                if coordinate > 0:
+                    return None
+                else:
+                    return AugExpr.format("{}.size()", span)
+
+            def stride(self, coordinate: int) -> AugExpr | None:
+                if coordinate > 0:
+                    return None
+                else:
+                    return AugExpr.format("1")
+
+        return Extraction()
+
+
+def std_span_ref(field: Field):
+    if field.spatial_dimensions > 1 or field.index_shape not in ((), (1,)):
+        raise SfgException(
+            "Only one-dimensional fields with trivial index dimensions can be mapped onto `std::span`"
+        )
+    return StdSpan(field.dtype, True, False).var(field.name)
diff --git a/src/pystencilssfg/lang/cpp/std_tuple.py b/src/pystencilssfg/lang/cpp/std_tuple.py
new file mode 100644
index 0000000000000000000000000000000000000000..82b2c4d169b23bf38bd08586dec5555aa901c9e9
--- /dev/null
+++ b/src/pystencilssfg/lang/cpp/std_tuple.py
@@ -0,0 +1,40 @@
+from typing import Sequence
+
+from pystencils.types import PsType, PsCustomType
+from pystencils.backend.kernelfunction import KernelParameter
+
+from ...lang import SrcVector, AugExpr
+from ...ir.source_components import SfgHeaderInclude
+
+
+class StdTuple(SrcVector):
+    def __init__(
+        self,
+        element_types: Sequence[PsType],
+        const: bool = False,
+        ref: bool = False,
+    ):
+        self._element_types = element_types
+        self._length = len(element_types)
+        elt_type_strings = tuple(t.c_string() for t in self._element_types)
+        tuple_type = f"{'const' if const else ''} std::tuple< {', '.join(elt_type_strings)} > {'&' if ref else ''}"
+        super().__init__(PsCustomType(tuple_type))
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return {SfgHeaderInclude("tuple", system_header=True)}
+
+    def extract_component(self, coordinate: int) -> AugExpr:
+        if coordinate < 0 or coordinate >= self._length:
+            raise ValueError(
+                f"Index {coordinate} out-of-bounds for std::tuple with {self._length} entries."
+            )
+
+        return AugExpr.format("std::get< {} >({})", coordinate, self)
+
+
+def std_tuple_ref(
+    identifier: str, components: Sequence[KernelParameter], const: bool = True
+):
+    elt_types = tuple(c.dtype for c in components)
+    return StdTuple(elt_types, const=const, ref=True).var(identifier)
diff --git a/src/pystencilssfg/lang/cpp/std_vector.py b/src/pystencilssfg/lang/cpp/std_vector.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3b4b52f91bcb79b6378051c541719e59048d3cb
--- /dev/null
+++ b/src/pystencilssfg/lang/cpp/std_vector.py
@@ -0,0 +1,56 @@
+from pystencils.field import Field
+from pystencils.types import PsType, PsCustomType
+
+from ...lang import SrcField, SrcVector, AugExpr, IFieldExtraction
+from ...ir.source_components import SfgHeaderInclude
+
+
+class StdVector(SrcVector, SrcField):
+    def __init__(
+        self,
+        T: PsType,
+        unsafe: bool = False,
+        reference: bool = True,
+    ):
+        typestring = f"std::vector< {(T.c_string())} > {'&' if reference else ''}"
+        super(StdVector, self).__init__(PsCustomType(typestring))
+
+        self._element_type = T
+        self._unsafe = unsafe
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return {
+            SfgHeaderInclude("vector", system_header=True),
+        }
+
+    def get_extraction(self) -> IFieldExtraction:
+        vec = self
+
+        class Extraction(IFieldExtraction):
+            def ptr(self) -> AugExpr:
+                return AugExpr.format("{}.data()", vec)
+
+            def size(self, coordinate: int) -> AugExpr | None:
+                if coordinate > 0:
+                    return None
+                else:
+                    return AugExpr.format("{}.size()", vec)
+
+            def stride(self, coordinate: int) -> AugExpr | None:
+                if coordinate > 0:
+                    return None
+                else:
+                    return AugExpr.format("1")
+
+        return Extraction()
+
+    def extract_component(self, coordinate: int) -> AugExpr:
+        if self._unsafe:
+            return AugExpr.format("{}[{}]", self, coordinate)
+        else:
+            return AugExpr.format("{}.at({})", self, coordinate)
+
+
+def std_vector_ref(field: Field):
+    return StdVector(field.dtype, unsafe=False, reference=True).var(field.name)
diff --git a/src/pystencilssfg/lang/expressions.py b/src/pystencilssfg/lang/expressions.py
new file mode 100644
index 0000000000000000000000000000000000000000..c456194a11fd6da541c96b76f2bf1e5b3a8bc984
--- /dev/null
+++ b/src/pystencilssfg/lang/expressions.py
@@ -0,0 +1,153 @@
+from __future__ import annotations
+from typing import Iterable
+from itertools import chain
+from abc import ABC, abstractmethod
+
+from pystencils.types import PsType
+
+from ..ir.source_components import SfgVar, SfgHeaderInclude
+from ..exceptions import SfgException
+
+
+class DependentExpression:
+    __match_args__ = ("expr", "depends")
+
+    def __init__(self, expr: str, depends: Iterable[SfgVar | AugExpr]):
+        self._expr: str = expr
+        deps: set[SfgVar] = set()
+        for obj in depends:
+            if isinstance(obj, AugExpr):
+                deps |= obj.depends
+            else:
+                deps.add(obj)
+
+        self._depends = tuple(deps)
+
+    @property
+    def expr(self) -> str:
+        return self._expr
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        return set(self._depends)
+
+    def __hash_contents__(self):
+        return (self._expr, self._depends)
+
+    def __eq__(self, other: object):
+        if not isinstance(other, DependentExpression):
+            return False
+
+        return self.__hash_contents__() == other.__hash_contents__()
+
+    def __hash__(self):
+        return hash(self.__hash_contents__())
+
+    def __str__(self) -> str:
+        return self.expr
+
+    def __add__(self, other: DependentExpression):
+        return DependentExpression(self.expr + other.expr, self.depends | other.depends)
+
+
+class AugExpr:
+    def __init__(self, dtype: PsType | None = None):
+        self._dtype = dtype
+        self._bound: DependentExpression | None = None
+
+    def var(self, name: str):
+        v = SfgVar(name, self.get_dtype(), self.required_includes)
+        expr = DependentExpression(name, (v,))
+        return self._bind(expr)
+
+    @staticmethod
+    def make(code: str, depends: Iterable[SfgVar | AugExpr]):
+        return AugExpr()._bind(DependentExpression(code, depends))
+
+    @staticmethod
+    def format(fmt: str, *deps, **kwdeps) -> AugExpr:
+        return AugExpr().bind(fmt, *deps, **kwdeps)
+
+    def bind(self, fmt: str, *deps, **kwdeps):
+        depends = filter(
+            lambda obj: isinstance(obj, (SfgVar, AugExpr)), chain(deps, kwdeps.values())
+        )
+        code = fmt.format(*deps, **kwdeps)
+        self._bind(DependentExpression(code, depends))
+        return self
+
+    def expr(self) -> DependentExpression:
+        if self._bound is None:
+            raise SfgException("No syntax bound to this AugExpr.")
+
+        return self._bound
+
+    @property
+    def depends(self) -> set[SfgVar]:
+        if self._bound is None:
+            raise SfgException("No syntax bound to this AugExpr.")
+
+        return self._bound.depends
+
+    @property
+    def dtype(self) -> PsType | None:
+        return self._dtype
+
+    def get_dtype(self) -> PsType:
+        if self._dtype is None:
+            raise SfgException("This AugExpr has no known data type.")
+
+        return self._dtype
+
+    @property
+    def required_includes(self) -> set[SfgHeaderInclude]:
+        return set()
+
+    def __str__(self) -> str:
+        if self._bound is None:
+            return "/* [ERROR] unbound AugExpr */"
+        else:
+            return str(self._bound)
+
+    def _bind(self, expr: DependentExpression):
+        if self._bound is not None:
+            raise SfgException("Attempting to bind an already-bound AugExpr.")
+
+        self._bound = expr
+        return self
+
+    def _is_bound(self) -> bool:
+        return self._bound is not None
+
+
+class IFieldExtraction(ABC):
+    """Interface for objects defining how to extract low-level field parameters
+    from high-level data structures."""
+
+    @abstractmethod
+    def ptr(self) -> AugExpr:
+        pass
+
+    @abstractmethod
+    def size(self, coordinate: int) -> AugExpr | None:
+        pass
+
+    @abstractmethod
+    def stride(self, coordinate: int) -> AugExpr | None:
+        pass
+
+
+class SrcField(AugExpr):
+    """Represents a C++ data structure that can be mapped to a *pystencils* field."""
+
+    @abstractmethod
+    def get_extraction(self) -> IFieldExtraction:
+        pass
+
+
+class SrcVector(AugExpr, ABC):
+    """Represents a C++ data structure that represents a mathematical vector."""
+
+    @abstractmethod
+    def extract_component(self, coordinate: int) -> AugExpr:
+        pass
diff --git a/src/pystencilssfg/source_concepts/__init__.py b/src/pystencilssfg/source_concepts/__init__.py
deleted file mode 100644
index aa4817f7b1bec919de6a68617e1e02d187c60686..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/source_concepts/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .source_objects import SrcObject, SrcField, SrcVector, TypedSymbolOrObject
-
-__all__ = ["SrcObject", "SrcField", "SrcVector", "TypedSymbolOrObject"]
diff --git a/src/pystencilssfg/source_concepts/cpp/std_mdspan.py b/src/pystencilssfg/source_concepts/cpp/std_mdspan.py
deleted file mode 100644
index 11645a536bf0064099ab44bff93e05376eb7ae13..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/source_concepts/cpp/std_mdspan.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from typing import Union, cast
-
-import numpy as np
-
-from pystencils import Field
-from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
-
-from ...tree import SfgStatements
-from ..source_objects import SrcField
-from ...source_components import SfgHeaderInclude
-from ...types import PsType, cpp_typename, SrcType
-from ...exceptions import SfgException
-
-
-class StdMdspan(SrcField):
-    dynamic_extent = "std::dynamic_extent"
-
-    def __init__(
-        self,
-        identifer: str,
-        T: PsType,
-        extents: tuple[int | str, ...],
-        extents_type: PsType = int,
-        reference: bool = False,
-    ):
-        cpp_typestr = cpp_typename(T)
-        extents_type_str = cpp_typename(extents_type)
-
-        extents_str = (
-            f"std::extents< {extents_type_str}, {', '.join(str(e) for e in extents)} >"
-        )
-        typestring = (
-            f"std::mdspan< {cpp_typestr}, {extents_str} > {'&' if reference else ''}"
-        )
-        super().__init__(identifer, SrcType(typestring))
-
-        self._extents = extents
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return {SfgHeaderInclude("experimental/mdspan", system_header=True)}
-
-    def extract_ptr(self, ptr_symbol: FieldPointerSymbol):
-        return SfgStatements(
-            f"{ptr_symbol.dtype} {ptr_symbol.name} = {self._identifier}.data_handle();",
-            (ptr_symbol,),
-            (self,),
-        )
-
-    def extract_size(
-        self, coordinate: int, size: Union[int, FieldShapeSymbol]
-    ) -> SfgStatements:
-        dim = len(self._extents)
-        if coordinate >= dim:
-            if isinstance(size, FieldShapeSymbol):
-                raise SfgException(
-                    f"Cannot extract size in coordinate {coordinate} from a {dim}-dimensional mdspan!"
-                )
-            elif size != 1:
-                raise SfgException(
-                    f"Cannot map field with size {size} in coordinate {coordinate} to {dim}-dimensional mdspan!"
-                )
-            else:
-                #   trivial trailing index dimensions are OK -> do nothing
-                return SfgStatements(
-                    f"// {self._identifier}.extents().extent({coordinate}) == 1", (), ()
-                )
-
-        if isinstance(size, FieldShapeSymbol):
-            return SfgStatements(
-                f"{size.dtype} {size.name} = {self._identifier}.extents().extent({coordinate});",
-                (size,),
-                (self,),
-            )
-        else:
-            return SfgStatements(
-                f"assert( {self._identifier}.extents().extent({coordinate}) == {size} );",
-                (),
-                (self,),
-            )
-
-    def extract_stride(
-        self, coordinate: int, stride: Union[int, FieldStrideSymbol]
-    ) -> SfgStatements:
-        if coordinate >= len(self._extents):
-            raise SfgException(
-                f"Cannot extract stride in coordinate {coordinate} from a {len(self._extents)}-dimensional mdspan"
-            )
-
-        if isinstance(stride, FieldStrideSymbol):
-            return SfgStatements(
-                f"{stride.dtype} {stride.name} = {self._identifier}.stride({coordinate});",
-                (stride,),
-                (self,),
-            )
-        else:
-            return SfgStatements(
-                f"assert( {self._identifier}.stride({coordinate}) == {stride} );",
-                (),
-                (self,),
-            )
-
-
-def mdspan_ref(field: Field, extents_type: type = np.uint32):
-    """Creates a `std::mdspan &` for a given pystencils field."""
-    from pystencils.field import layout_string_to_tuple
-
-    if field.layout != layout_string_to_tuple("soa", field.spatial_dimensions):
-        raise NotImplementedError(
-            "mdspan mapping is currently only available for structure-of-arrays fields"
-        )
-
-    extents: list[str | int] = []
-
-    for s in field.spatial_shape:
-        extents.append(
-            StdMdspan.dynamic_extent
-            if isinstance(s, FieldShapeSymbol)
-            else cast(int, s)
-        )
-
-    if field.index_shape != (1,):
-        for s in field.index_shape:
-            extents += (
-                StdMdspan.dynamic_extent if isinstance(s, FieldShapeSymbol) else s
-            )
-
-    return StdMdspan(
-        field.name,
-        field.dtype,
-        tuple(extents),
-        extents_type=extents_type,
-        reference=True,
-    )
diff --git a/src/pystencilssfg/source_concepts/cpp/std_tuple.py b/src/pystencilssfg/source_concepts/cpp/std_tuple.py
deleted file mode 100644
index 717a83c4e8b5918ba80d6398de8e625285a6fab2..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/source_concepts/cpp/std_tuple.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from typing import Sequence
-
-from pystencils.typing import BasicType, TypedSymbol
-
-from ...tree import SfgStatements
-from ..source_objects import SrcVector
-from ..source_objects import TypedSymbolOrObject
-from ...types import SrcType, cpp_typename
-from ...source_components import SfgHeaderInclude
-
-
-class StdTuple(SrcVector):
-    def __init__(
-        self,
-        identifier: str,
-        element_types: Sequence[BasicType],
-        const: bool = False,
-        ref: bool = False,
-    ):
-        self._element_types = element_types
-        self._length = len(element_types)
-        elt_type_strings = tuple(cpp_typename(t) for t in self._element_types)
-        src_type = f"{'const' if const else ''} std::tuple< {', '.join(elt_type_strings)} > {'&' if ref else ''}"
-        super().__init__(identifier, SrcType(src_type))
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return {SfgHeaderInclude("tuple", system_header=True)}
-
-    def extract_component(self, destination: TypedSymbolOrObject, coordinate: int):
-        if coordinate < 0 or coordinate >= self._length:
-            raise ValueError(
-                f"Index {coordinate} out-of-bounds for std::tuple with {self._length} entries."
-            )
-
-        if destination.dtype != self._element_types[coordinate]:
-            raise ValueError(
-                f"Cannot extract type {destination.dtype} from std::tuple entry "
-                "of type {self._element_types[coordinate]}"
-            )
-
-        return SfgStatements(
-            f"{destination.dtype} {destination.name} = std::get< {coordinate} >({self.identifier});",
-            (destination,),
-            (self,),
-        )
-
-
-def std_tuple_ref(
-    identifier: str, components: Sequence[TypedSymbol], const: bool = True
-):
-    elt_types = tuple(c.dtype for c in components)
-    return StdTuple(identifier, elt_types, const=const, ref=True)
diff --git a/src/pystencilssfg/source_concepts/cpp/std_vector.py b/src/pystencilssfg/source_concepts/cpp/std_vector.py
deleted file mode 100644
index a63220607c895f7577035015a512a7f67d6ea7b0..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/source_concepts/cpp/std_vector.py
+++ /dev/null
@@ -1,119 +0,0 @@
-from typing import Union
-
-from pystencils.field import Field, FieldType
-from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
-
-from ...tree import SfgStatements
-from ..source_objects import SrcField, SrcVector
-from ..source_objects import TypedSymbolOrObject
-from ...types import SrcType, PsType, cpp_typename
-from ...source_components import SfgHeaderInclude, SfgClass
-from ...exceptions import SfgException
-
-
-class StdVector(SrcVector, SrcField):
-    def __init__(
-        self,
-        identifer: str,
-        T: Union[SrcType, PsType],
-        unsafe: bool = False,
-        reference: bool = True,
-    ):
-        typestring = f"std::vector< {cpp_typename(T)} > {'&' if reference else ''}"
-        super(StdVector, self).__init__(identifer, SrcType(typestring))
-
-        self._element_type = T
-        self._unsafe = unsafe
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return {
-            SfgHeaderInclude("cassert", system_header=True),
-            SfgHeaderInclude("vector", system_header=True),
-        }
-
-    def extract_ptr(self, ptr_symbol: FieldPointerSymbol):
-        if ptr_symbol.dtype != self._element_type:
-            if self._unsafe:
-                mapping = f"{ptr_symbol.dtype} {ptr_symbol.name} = ({ptr_symbol.dtype}) {self._identifier}.data();"
-            else:
-                raise SfgException(
-                    "Field type and std::vector element type do not match, and unsafe extraction was not enabled."
-                )
-        else:
-            mapping = (
-                f"{ptr_symbol.dtype} {ptr_symbol.name} = {self._identifier}.data();"
-            )
-
-        return SfgStatements(mapping, (ptr_symbol,), (self,))
-
-    def extract_size(
-        self, coordinate: int, size: Union[int, FieldShapeSymbol]
-    ) -> SfgStatements:
-        if coordinate > 0:
-            if isinstance(size, FieldShapeSymbol):
-                raise SfgException(
-                    f"Cannot extract size in coordinate {coordinate} from std::vector!"
-                )
-            elif size != 1:
-                raise SfgException(
-                    f"Cannot map field with size {size} in coordinate {coordinate} to std::vector!"
-                )
-            else:
-                #   trivial trailing index dimensions are OK -> do nothing
-                return SfgStatements(
-                    f"// {self._identifier}.size({coordinate}) == 1", (), ()
-                )
-
-        if isinstance(size, FieldShapeSymbol):
-            return SfgStatements(
-                f"{size.dtype} {size.name} = ({size.dtype}) {self._identifier}.size();",
-                (size,),
-                (self,),
-            )
-        else:
-            return SfgStatements(
-                f"assert( {self._identifier}.size() == {size} );", (), (self,)
-            )
-
-    def extract_stride(
-        self, coordinate: int, stride: Union[int, FieldStrideSymbol]
-    ) -> SfgStatements:
-        if coordinate == 1:
-            if stride != 1:
-                raise SfgException(
-                    "Can only map fields with trivial index stride onto std::vector!"
-                )
-
-        if coordinate > 1:
-            raise SfgException(
-                f"Cannot extract stride in coordinate {coordinate} from std::vector"
-            )
-
-        if isinstance(stride, FieldStrideSymbol):
-            return SfgStatements(f"{stride.dtype} {stride.name} = 1;", (stride,), ())
-        elif stride != 1:
-            raise SfgException(
-                "Can only map fields with trivial strides onto std::vector!"
-            )
-        else:
-            return SfgStatements(
-                f"// {self._identifier}.stride({coordinate}) == 1", (), ()
-            )
-
-    def extract_component(
-        self, destination: TypedSymbolOrObject, coordinate: int
-    ) -> SfgStatements:
-        if self._unsafe:
-            mapping = f"{destination.dtype} {destination.name} = {self._identifier}[{coordinate}];"
-        else:
-            mapping = f"{destination.dtype} {destination.name} = {self._identifier}.at({coordinate});"
-
-        return SfgStatements(mapping, (destination,), (self,))
-
-
-def std_vector_ref(field: Field, src_struct: SfgClass):
-    if field.field_type != FieldType.INDEXED:
-        raise ValueError("Can only create std::vector for index fields")
-
-    return StdVector(field.name, src_struct.src_type, unsafe=True, reference=True)
diff --git a/src/pystencilssfg/source_concepts/source_objects.py b/src/pystencilssfg/source_concepts/source_objects.py
deleted file mode 100644
index 584d20ca700f9e4835be7f240ff31ffd1c029905..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/source_concepts/source_objects.py
+++ /dev/null
@@ -1,122 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, Union, TypeAlias
-
-from abc import ABC, abstractmethod
-
-from pystencils import TypedSymbol, Field
-from pystencils.typing import FieldPointerSymbol, FieldStrideSymbol, FieldShapeSymbol
-
-from ..types import SrcType
-
-if TYPE_CHECKING:
-    from ..source_components import SfgHeaderInclude
-    from ..tree import SfgStatements, SfgSequence
-
-
-class SrcObject:
-    """C/C++ object of nonprimitive type.
-
-    Two objects are identical if they have the same identifier and type string."""
-
-    def __init__(self, identifier: str, src_type: SrcType):
-        self._identifier = identifier
-        self._src_type = src_type
-
-    @property
-    def identifier(self):
-        return self._identifier
-
-    @property
-    def name(self) -> str:
-        """For interface compatibility with ps.TypedSymbol"""
-        return self._identifier
-
-    @property
-    def dtype(self):
-        return self._src_type
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return set()
-
-    def __hash__(self) -> int:
-        return hash((self._identifier, self._src_type))
-
-    def __eq__(self, other: object) -> bool:
-        return (
-            isinstance(other, SrcObject)
-            and self._identifier == other._identifier
-            and self._src_type == other._src_type
-        )
-
-    def __str__(self) -> str:
-        return self.name
-
-
-TypedSymbolOrObject: TypeAlias = TypedSymbol | SrcObject
-
-
-class SrcField(SrcObject, ABC):
-    """Represents a C++ data structure that can be mapped to a *pystencils* field.
-
-    Subclasses of `SrcField` are meant to be used in [SfgComposer.map_field][pystencilssfg.SfgComposer.map_field]
-    to produce the necessary mapping code from a high-level C++ field data structure to a pystencils field.
-
-    Subclasses of `SrcField` must implement `extract_ptr`, `extract_size` and `extract_stride`
-    to emit code extracting field pointers and indexing information from the high-level concept.
-
-    Currently, *pystencils-sfg* provides an implementation for the C++ `std::vector` and `std::mdspan` classes via
-    [StdVector][pystencilssfg.source_concepts.cpp.StdVector] and
-    [StdMdspan][pystencilssfg.source_concepts.cpp.StdMdspan].
-    """
-
-    def __init__(self, identifier: str, src_type: SrcType):
-        super().__init__(identifier, src_type)
-
-    @abstractmethod
-    def extract_ptr(self, ptr_symbol: FieldPointerSymbol) -> SfgStatements:
-        pass
-
-    @abstractmethod
-    def extract_size(
-        self, coordinate: int, size: Union[int, FieldShapeSymbol]
-    ) -> SfgStatements:
-        pass
-
-    @abstractmethod
-    def extract_stride(
-        self, coordinate: int, stride: Union[int, FieldStrideSymbol]
-    ) -> SfgStatements:
-        pass
-
-    def extract_parameters(self, field: Field) -> SfgSequence:
-        ptr = FieldPointerSymbol(field.name, field.dtype, False)
-
-        from ..composer import make_sequence
-
-        return make_sequence(
-            self.extract_ptr(ptr),
-            *(self.extract_size(c, s) for c, s in enumerate(field.shape)),
-            *(self.extract_stride(c, s) for c, s in enumerate(field.strides)),
-        )
-
-
-class SrcVector(SrcObject, ABC):
-    """Represents a C++ abstraction of a mathematical vector that can be mapped to a vector of symbols.
-
-    Subclasses of `SrcVector` are meant to be used in [SfgComposer.map_vector][pystencilssfg.SfgComposer.map_vector]
-    to produce the necessary mapping code from a high-level C++ vector data structure to a vector of symbols.
-
-    Subclasses of `SrcVector` must implement `extract_component` to emit code extracting scalar values
-    from the high-level vector.
-
-    Currently, *pystencils-sfg* provides an implementation for the C++ `std::vector` via
-    [StdVector][pystencilssfg.source_concepts.cpp.StdVector].
-    """
-
-    @abstractmethod
-    def extract_component(
-        self, destination: TypedSymbolOrObject, coordinate: int
-    ) -> SfgStatements:
-        pass
diff --git a/src/pystencilssfg/tree/__init__.py b/src/pystencilssfg/tree/__init__.py
deleted file mode 100644
index 15cd329c57a0d1c74a8492f0fb74204693bd277f..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/tree/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from .basic_nodes import (
-    SfgCallTreeNode,
-    SfgCallTreeLeaf,
-    SfgEmptyNode,
-    SfgKernelCallNode,
-    SfgBlock,
-    SfgSequence,
-    SfgStatements,
-    SfgFunctionParams,
-    SfgRequireIncludes,
-)
-from .conditional import SfgBranch, SfgCondition, IntEven, IntOdd
-
-__all__ = [
-    "SfgCallTreeNode",
-    "SfgCallTreeLeaf",
-    "SfgEmptyNode",
-    "SfgKernelCallNode",
-    "SfgSequence",
-    "SfgBlock",
-    "SfgStatements",
-    "SfgFunctionParams",
-    "SfgRequireIncludes",
-    "SfgCondition",
-    "SfgBranch",
-    "IntEven",
-    "IntOdd",
-]
diff --git a/src/pystencilssfg/tree/basic_nodes.py b/src/pystencilssfg/tree/basic_nodes.py
deleted file mode 100644
index 26fe8ed49873cf60c46fb5f6f0bce98fbeed1d50..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/tree/basic_nodes.py
+++ /dev/null
@@ -1,226 +0,0 @@
-from __future__ import annotations
-from typing import TYPE_CHECKING, Sequence
-
-from abc import ABC, abstractmethod
-from itertools import chain
-
-from ..source_components import SfgHeaderInclude, SfgKernelHandle
-from ..source_concepts.source_objects import SrcObject, TypedSymbolOrObject
-
-if TYPE_CHECKING:
-    from ..context import SfgContext
-
-
-class SfgCallTreeNode(ABC):
-    """Base class for all nodes comprising SFG call trees.
-
-    ## Code Printing
-
-    For extensibility, code printing is implemented inside the call tree.
-    Therefore, every instantiable call tree node must implement the method `get_code`.
-    By convention, the string returned by `get_code` should not contain a trailing newline.
-
-    ## Branching Structure
-
-    The branching structure of the call tree is managed uniformly through the `children` interface
-    of SfgCallTreeNode. Each subclass must ensure that access to and modification of
-    the branching structure through the `children` property and the `child` and `set_child`
-    methods is possible, if necessary by overriding the property and methods.
-    """
-
-    def __init__(self, *children: SfgCallTreeNode):
-        self._children = list(children)
-
-    @property
-    def children(self) -> tuple[SfgCallTreeNode, ...]:
-        """This node's children"""
-        return tuple(self._children)
-
-    @children.setter
-    def children(self, cs: Sequence[SfgCallTreeNode]) -> None:
-        """Replaces this node's children. By default, the number of child nodes must not change."""
-        if len(cs) != len(self._children):
-            raise ValueError("The number of child nodes must remain the same!")
-        self._children = list(cs)
-
-    def child(self, idx: int) -> SfgCallTreeNode:
-        """Gets the child at index idx."""
-        return self._children[idx]
-
-    def set_child(self, idx: int, c: SfgCallTreeNode):
-        """Replaces the child at index idx."""
-        self._children[idx] = c
-
-    def __getitem__(self, idx: int) -> SfgCallTreeNode:
-        return self.child(idx)
-
-    def __setitem__(self, idx: int, c: SfgCallTreeNode) -> None:
-        self.set_child(idx, c)
-
-    @abstractmethod
-    def get_code(self, ctx: SfgContext) -> str:
-        """Returns the code of this node.
-
-        By convention, the code block emitted by this function should not contain a trailing newline.
-        """
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        """Return a set of header includes required by this node"""
-        return set()
-
-
-class SfgCallTreeLeaf(SfgCallTreeNode, ABC):
-    """A leaf node of the call tree.
-
-    Leaf nodes must implement `required_parameters` for automatic parameter collection.
-    """
-
-    @property
-    @abstractmethod
-    def required_parameters(self) -> set[TypedSymbolOrObject]: ...
-
-
-class SfgEmptyNode(SfgCallTreeLeaf):
-    """A leaf node that does not emit any code.
-
-    Empty nodes must still implement `required_parameters`.
-    """
-
-    def __init__(self):
-        super().__init__()
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return ""
-
-
-class SfgStatements(SfgCallTreeLeaf):
-    """Represents (a sequence of) statements in the source language.
-
-    This class groups together arbitrary code strings
-    (e.g. sequences of C++ statements, cf. https://en.cppreference.com/w/cpp/language/statements),
-    and annotates them with the set of symbols read and written by these statements.
-
-    It is the user's responsibility to ensure that the code string is valid code in the output language,
-    and that the lists of required and defined objects are correct and complete.
-
-    Args:
-        code_string: Code to be printed out.
-        defined_params: Objects (as `SrcObject` or `TypedSymbol`) that will be newly defined and visible to
-            code in sequence after these statements.
-        required_params: Objects (as `SrcObject` or `TypedSymbol`) that are required as input to these statements.
-    """
-
-    def __init__(
-        self,
-        code_string: str,
-        defined_params: Sequence[TypedSymbolOrObject],
-        required_params: Sequence[TypedSymbolOrObject],
-    ):
-        super().__init__()
-
-        self._code_string = code_string
-
-        self._defined_params = set(defined_params)
-        self._required_params = set(required_params)
-
-        self._required_includes = set()
-        for obj in chain(required_params, defined_params):
-            if isinstance(obj, SrcObject):
-                self._required_includes |= obj.required_includes
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return self._required_params
-
-    @property
-    def defined_parameters(self) -> set[TypedSymbolOrObject]:
-        return self._defined_params
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return self._required_includes
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return self._code_string
-
-
-class SfgFunctionParams(SfgEmptyNode):
-    def __init__(self, parameters: Sequence[TypedSymbolOrObject]):
-        super().__init__()
-        self._params = set(parameters)
-
-        self._required_includes = set()
-        for obj in parameters:
-            if isinstance(obj, SrcObject):
-                self._required_includes |= obj.required_includes
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return self._params
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return self._required_includes
-
-
-class SfgRequireIncludes(SfgEmptyNode):
-    def __init__(self, includes: Sequence[SfgHeaderInclude]):
-        super().__init__()
-        self._required_includes = set(includes)
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return set()
-
-    @property
-    def required_includes(self) -> set[SfgHeaderInclude]:
-        return self._required_includes
-
-
-class SfgSequence(SfgCallTreeNode):
-    def __init__(self, children: Sequence[SfgCallTreeNode]):
-        super().__init__(*children)
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return "\n".join(c.get_code(ctx) for c in self._children)
-
-
-class SfgBlock(SfgCallTreeNode):
-    def __init__(self, subtree: SfgCallTreeNode):
-        super().__init__(subtree)
-
-    @property
-    def subtree(self) -> SfgCallTreeNode:
-        return self._children[0]
-
-    def get_code(self, ctx: SfgContext) -> str:
-        subtree_code = ctx.codestyle.indent(self.subtree.get_code(ctx))
-
-        return "{\n" + subtree_code + "\n}"
-
-
-# class SfgForLoop(SfgCallTreeNode):
-#     def __init__(self, control_line: SfgStatements, body: SfgCallTreeNode):
-#         super().__init__(control_line, body)
-
-#     @property
-#     def body(self) -> SfgStatements:
-#         return cast(SfgStatements)
-
-
-class SfgKernelCallNode(SfgCallTreeLeaf):
-    def __init__(self, kernel_handle: SfgKernelHandle):
-        super().__init__()
-        self._kernel_handle = kernel_handle
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return set(p.symbol for p in self._kernel_handle.parameters)
-
-    def get_code(self, ctx: SfgContext) -> str:
-        ast_params = self._kernel_handle.parameters
-        fnc_name = self._kernel_handle.fully_qualified_name
-        call_parameters = ", ".join([p.symbol.name for p in ast_params])
-
-        return f"{fnc_name}({call_parameters});"
diff --git a/src/pystencilssfg/tree/conditional.py b/src/pystencilssfg/tree/conditional.py
deleted file mode 100644
index 65f1f87e827ca137b6497ce29a2865eef1cb6159..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/tree/conditional.py
+++ /dev/null
@@ -1,200 +0,0 @@
-from __future__ import annotations
-from typing import TYPE_CHECKING, Optional, cast, Generator, Sequence, NewType
-
-from pystencils.typing import TypedSymbol, BasicType
-
-from .basic_nodes import SfgCallTreeNode, SfgCallTreeLeaf
-from ..source_concepts.source_objects import TypedSymbolOrObject
-
-if TYPE_CHECKING:
-    from ..context import SfgContext
-
-
-class SfgCondition(SfgCallTreeLeaf):
-    pass
-
-
-class SfgCustomCondition(SfgCondition):
-    def __init__(self, cond_text: str):
-        super().__init__()
-        self._cond_text = cond_text
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return set()
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return self._cond_text
-
-
-class IntEven(SfgCondition):
-    def __init__(self, symbol: TypedSymbol):
-        super().__init__()
-        if not isinstance(symbol.dtype, BasicType) or not symbol.dtype.is_int():
-            raise ValueError(f"Symbol {symbol} does not have integer type.")
-
-        self._symbol = symbol
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return {self._symbol}
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return f"(({self._symbol.name} & 1) ^ 1)"
-
-
-class IntOdd(SfgCondition):
-    def __init__(self, symbol: TypedSymbol):
-        super().__init__()
-        if not isinstance(symbol.dtype, BasicType) or not symbol.dtype.is_int():
-            raise ValueError(f"Symbol {symbol} does not have integer type.")
-
-        self._symbol = symbol
-
-    @property
-    def required_parameters(self) -> set[TypedSymbolOrObject]:
-        return {self._symbol}
-
-    def get_code(self, ctx: SfgContext) -> str:
-        return f"({self._symbol.name} & 1)"
-
-
-class SfgBranch(SfgCallTreeNode):
-    def __init__(
-        self,
-        cond: SfgCondition,
-        branch_true: SfgCallTreeNode,
-        branch_false: Optional[SfgCallTreeNode] = None,
-    ):
-        super().__init__(cond, branch_true, *((branch_false,) if branch_false else ()))
-
-    @property
-    def condition(self) -> SfgCondition:
-        return cast(SfgCondition, self._children[0])
-
-    @property
-    def branch_true(self) -> SfgCallTreeNode:
-        return self._children[1]
-
-    @property
-    def branch_false(self) -> SfgCallTreeNode:
-        return self._children[2]
-
-    def get_code(self, ctx: SfgContext) -> str:
-        code = f"if({self.condition.get_code(ctx)}) {{\n"
-        code += ctx.codestyle.indent(self.branch_true.get_code(ctx))
-        code += "\n}"
-
-        if self.branch_false is not None:
-            code += "else {\n"
-            code += ctx.codestyle.indent(self.branch_false.get_code(ctx))
-            code += "\n}"
-
-        return code
-
-
-class SfgSwitchCase(SfgCallTreeNode):
-    DefaultCaseType = NewType("DefaultCaseType", object)
-    Default = DefaultCaseType(object())
-
-    def __init__(self, label: str | DefaultCaseType, body: SfgCallTreeNode):
-        self._label = label
-        super().__init__(body)
-
-    @property
-    def label(self) -> str | DefaultCaseType:
-        return self._label
-
-    @property
-    def body(self) -> SfgCallTreeNode:
-        return self._children[0]
-
-    @property
-    def is_default(self) -> bool:
-        return self._label == SfgSwitchCase.Default
-
-    def get_code(self, ctx: SfgContext) -> str:
-        code = ""
-        if self._label == SfgSwitchCase.Default:
-            code += "default: {\n"
-        else:
-            code += f"case {self._label}: {{\n"
-        code += ctx.codestyle.indent(self.body.get_code(ctx))
-        code += "\nbreak;\n}"
-        return code
-
-
-class SfgSwitch(SfgCallTreeNode):
-    def __init__(
-        self,
-        switch_arg: str | TypedSymbolOrObject,
-        cases_dict: dict[str, SfgCallTreeNode],
-        default: SfgCallTreeNode | None = None,
-    ):
-        children = [SfgSwitchCase(label, body) for label, body in cases_dict.items()]
-        if default is not None:
-            # invariant: the default case is always the last child
-            children += [SfgSwitchCase(SfgSwitchCase.Default, default)]
-        self._switch_arg = switch_arg
-        self._default = default
-        super().__init__(*children)
-
-    @property
-    def switch_arg(self) -> str | TypedSymbolOrObject:
-        return self._switch_arg
-
-    def cases(self) -> Generator[SfgCallTreeNode, None, None]:
-        if self._default is not None:
-            yield from self._children[:-1]
-        else:
-            yield from self._children
-
-    @property
-    def default(self) -> SfgCallTreeNode | None:
-        return self._default
-
-    @property
-    def children(self) -> tuple[SfgCallTreeNode, ...]:
-        return tuple(self._children)
-
-    @children.setter
-    def children(self, cs: Sequence[SfgCallTreeNode]) -> None:
-        if len(cs) != len(self._children):
-            raise ValueError("The number of child nodes must remain the same!")
-
-        self._default = None
-        for i, c in enumerate(cs):
-            if not isinstance(c, SfgSwitchCase):
-                raise ValueError(
-                    "An SfgSwitch node can only have SfgSwitchCases as children."
-                )
-            if c.is_default:
-                if i != len(cs) - 1:
-                    raise ValueError("Default case must be listed last.")
-                else:
-                    self._default = c
-
-        self._children = list(cs)
-
-    def set_child(self, idx: int, c: SfgCallTreeNode):
-        if not isinstance(c, SfgSwitchCase):
-            raise ValueError(
-                "An SfgSwitch node can only have SfgSwitchCases as children."
-            )
-
-        if c.is_default:
-            if idx != len(self._children) - 1:
-                raise ValueError("Default case must be the last child.")
-            elif self._default is None:
-                raise ValueError("Cannot replace normal case with default case.")
-            else:
-                self._default = c
-                self._children[-1] = c
-        else:
-            self._children[idx] = c
-
-    def get_code(self, ctx: SfgContext) -> str:
-        code = f"switch({self._switch_arg}) {{\n"
-        code += "\n".join(c.get_code(ctx) for c in self.children)
-        code += "}"
-        return code
diff --git a/src/pystencilssfg/tree/deferred_nodes.py b/src/pystencilssfg/tree/deferred_nodes.py
deleted file mode 100644
index 040b51b4c87a168873bbf03d644be85f716087a6..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/tree/deferred_nodes.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from __future__ import annotations
-from typing import TYPE_CHECKING
-
-from abc import ABC, abstractmethod
-
-from pystencils import Field
-from pystencils.typing import FieldPointerSymbol, FieldShapeSymbol, FieldStrideSymbol
-
-from ..exceptions import SfgException
-
-from .basic_nodes import SfgCallTreeNode, SfgSequence
-
-from ..source_concepts import SrcField
-from ..source_concepts.source_objects import TypedSymbolOrObject
-
-if TYPE_CHECKING:
-    from ..context import SfgContext
-
-
-class SfgDeferredNode(SfgCallTreeNode, ABC):
-    """Nodes of this type are inserted as placeholders into the kernel call tree
-    and need to be expanded at a later time.
-
-    Subclasses of SfgDeferredNode correspond to nodes that cannot be created yet
-    because information required for their construction is not yet known.
-    """
-
-    class InvalidAccess:
-        def __get__(self):
-            raise SfgException(
-                "Invalid access into deferred node; deferred nodes must be expanded first."
-            )
-
-    def __init__(self):
-        self._children = SfgDeferredNode.InvalidAccess
-
-    def get_code(self, ctx: SfgContext) -> str:
-        raise SfgException(
-            "Invalid access into deferred node; deferred nodes must be expanded first."
-        )
-
-
-class SfgParamCollectionDeferredNode(SfgDeferredNode, ABC):
-    @abstractmethod
-    def expand(self, visible_params: set[TypedSymbolOrObject]) -> SfgCallTreeNode: ...
-
-
-class SfgDeferredFieldMapping(SfgParamCollectionDeferredNode):
-    def __init__(self, field: Field, src_field: SrcField):
-        self._field = field
-        self._src_field = src_field
-
-    def expand(self, visible_params: set[TypedSymbolOrObject]) -> SfgCallTreeNode:
-        #    Find field pointer
-        ptr = None
-        for param in visible_params:
-            if (
-                isinstance(param, FieldPointerSymbol)
-                and param.field_name == self._field.name
-            ):
-                if param.dtype.base_type != self._field.dtype:
-                    raise SfgException(
-                        "Data type mismatch between field and encountered pointer symbol"
-                    )
-                ptr = param
-
-        #   Find required sizes
-        shape = []
-        for c, s in enumerate(self._field.shape):
-            if isinstance(s, FieldShapeSymbol) and s not in visible_params:
-                continue
-            else:
-                shape.append((c, s))
-
-        #   Find required strides
-        strides = []
-        for c, s in enumerate(self._field.strides):
-            if isinstance(s, FieldStrideSymbol) and s not in visible_params:
-                continue
-            else:
-                strides.append((c, s))
-
-        nodes = []
-
-        if ptr is not None:
-            nodes += [self._src_field.extract_ptr(ptr)]
-
-        nodes += [self._src_field.extract_size(c, s) for c, s in shape]
-        nodes += [self._src_field.extract_stride(c, s) for c, s in strides]
-
-        return SfgSequence(nodes)
diff --git a/src/pystencilssfg/types.py b/src/pystencilssfg/types.py
deleted file mode 100644
index 49367985c66c5e034954c6807ff91170b333e69c..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/types.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from typing import Union, TypeAlias, NewType
-import numpy as np
-
-from pystencils.typing import AbstractType, numpy_name_to_c
-
-
-PsType: TypeAlias = Union[type, np.dtype, AbstractType]
-"""Types used in interacting with pystencils.
-
-PsType represents various ways of specifying types within pystencils.
-In particular, it encompasses most ways to construct an instance of `AbstractType`,
-for example via `create_type`.
-
-(Note that, while `create_type` does accept strings, they are excluded here for
-reasons of safety. It is discouraged to use strings for type specifications when working
-with pystencils!)
-
-PsType is a temporary solution and will be removed in the future
-in favor of the consolidated pystencils backend typing system.
-"""
-
-SrcType = NewType("SrcType", str)
-"""C/C++-Types occuring during source file generation.
-
-When necessary, the SFG package checks equality of types by their name strings; it does
-not care about typedefs, aliases, namespaces, etc!
-
-SrcType is a temporary solution and will be removed in the future
-in favor of the consolidated pystencils backend typing system.
-"""
-
-
-def cpp_typename(type_obj: Union[str, SrcType, PsType]):
-    """Accepts type specifications in various ways and returns a valid typename to be used in code."""
-    # if isinstance(type_obj, str):
-    #     return type_obj
-    if isinstance(type_obj, str):
-        return type_obj
-    elif isinstance(type_obj, AbstractType):
-        return str(type_obj)
-    elif isinstance(type_obj, np.dtype) or isinstance(type_obj, type):
-        return numpy_name_to_c(np.dtype(type_obj).name)
-    else:
-        raise ValueError(f"Don't know how to interpret type object {type_obj}.")
diff --git a/src/pystencilssfg/visitors/__init__.py b/src/pystencilssfg/visitors/__init__.py
index 48c673a164e3567b2f694decd7816d30be37eedf..fc7af1b6363bdb1167ee6c0e164ba87e31fbb6b0 100644
--- a/src/pystencilssfg/visitors/__init__.py
+++ b/src/pystencilssfg/visitors/__init__.py
@@ -1,10 +1,5 @@
 from .dispatcher import visitor
-from .collectors import CollectIncludes
-from .tree_visitors import FlattenSequences, ExpandingParameterCollector
 
 __all__ = [
     "visitor",
-    "CollectIncludes",
-    "FlattenSequences",
-    "ExpandingParameterCollector",
 ]
diff --git a/src/pystencilssfg/visitors/collectors.py b/src/pystencilssfg/visitors/collectors.py
deleted file mode 100644
index fef8955bb8a5eab7c80d2c812b41e64ce7f938ba..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/visitors/collectors.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from functools import reduce
-
-from .dispatcher import visitor
-from ..exceptions import SfgException
-from ..tree import SfgCallTreeNode
-from ..source_components import (
-    SfgFunction,
-    SfgClass,
-    SfgConstructor,
-    SfgMemberVariable,
-    SfgInClassDefinition,
-)
-from ..context import SfgContext
-
-if TYPE_CHECKING:
-    from ..source_components import SfgHeaderInclude
-
-
-class CollectIncludes:
-    @visitor
-    def visit(self, obj: object) -> set[SfgHeaderInclude]:
-        raise SfgException(f"Can't collect includes from object of type {type(obj)}")
-
-    @visit.case(SfgContext)
-    def context(self, ctx: SfgContext) -> set[SfgHeaderInclude]:
-        includes = set()
-        for func in ctx.functions():
-            includes |= self.visit(func)
-
-        for cls in ctx.classes():
-            includes |= self.visit(cls)
-
-        return includes
-
-    @visit.case(SfgCallTreeNode)
-    def tree_node(self, node: SfgCallTreeNode) -> set[SfgHeaderInclude]:
-        return reduce(
-            lambda accu, child: accu | self.visit(child),
-            node.children,
-            node.required_includes,
-        )
-
-    @visit.case(SfgFunction)
-    def sfg_function(self, func: SfgFunction) -> set[SfgHeaderInclude]:
-        return self.visit(func.tree)
-
-    @visit.case(SfgClass)
-    def sfg_class(self, cls: SfgClass) -> set[SfgHeaderInclude]:
-        return reduce(
-            lambda accu, member: accu | (self.visit(member)), cls.members(), set()
-        )
-
-    @visit.case(SfgConstructor)
-    def sfg_constructor(self, constr: SfgConstructor) -> set[SfgHeaderInclude]:
-        return reduce(
-            lambda accu, obj: accu | obj.required_includes, constr.parameters, set()
-        )
-
-    @visit.case(SfgMemberVariable)
-    def sfg_member_var(self, var: SfgMemberVariable) -> set[SfgHeaderInclude]:
-        return var.required_includes
-
-    @visit.case(SfgInClassDefinition)
-    def sfg_cls_def(self, _: SfgInClassDefinition) -> set[SfgHeaderInclude]:
-        return set()
diff --git a/src/pystencilssfg/visitors/tree_visitors.py b/src/pystencilssfg/visitors/tree_visitors.py
deleted file mode 100644
index bb596b5132fd44eda9d30947d2dac3714c7f9430..0000000000000000000000000000000000000000
--- a/src/pystencilssfg/visitors/tree_visitors.py
+++ /dev/null
@@ -1,141 +0,0 @@
-from __future__ import annotations
-
-# from typing import TYPE_CHECKING
-
-from functools import reduce
-
-from ..tree.basic_nodes import (
-    SfgCallTreeNode,
-    SfgCallTreeLeaf,
-    SfgSequence,
-    SfgStatements,
-)
-from ..tree.deferred_nodes import SfgParamCollectionDeferredNode
-from ..tree.conditional import SfgSwitch
-from .dispatcher import visitor
-from ..source_concepts.source_objects import TypedSymbol, SrcObject, TypedSymbolOrObject
-
-
-class FlattenSequences:
-    """Flattens any nested sequences occuring in a kernel call tree."""
-
-    @visitor
-    def visit(self, node: SfgCallTreeNode) -> None:
-        for c in node.children:
-            self.visit(c)
-
-    @visit.case(SfgSequence)
-    def sequence(self, sequence: SfgSequence) -> None:
-        children_flattened: list[SfgCallTreeNode] = []
-
-        def flatten(seq: SfgSequence):
-            for c in seq.children:
-                if isinstance(c, SfgSequence):
-                    flatten(c)
-                else:
-                    children_flattened.append(c)
-
-        flatten(sequence)
-
-        for c in children_flattened:
-            self.visit(c)
-
-        sequence._children = children_flattened
-
-
-class ExpandingParameterCollector:
-    """Collects all parameters required but not defined in a kernel call tree.
-    Expands any deferred nodes of type `SfgParamCollectionDeferredNode` found within sequences on the way.
-    """
-
-    def __init__(self) -> None:
-        self._flattener = FlattenSequences()
-
-    @visitor
-    def visit(self, node: SfgCallTreeNode) -> set[TypedSymbolOrObject]:
-        return self.branching_node(node)
-
-    @visit.case(SfgCallTreeLeaf)
-    def leaf(self, leaf: SfgCallTreeLeaf) -> set[TypedSymbolOrObject]:
-        return leaf.required_parameters
-
-    @visit.case(SfgSwitch)
-    def switch(self, sw: SfgSwitch) -> set[TypedSymbolOrObject]:
-        params = self.branching_node(sw)
-        if isinstance(sw.switch_arg, (TypedSymbol, SrcObject)):
-            params.add(sw.switch_arg)
-        return params
-
-    @visit.case(SfgSequence)
-    def sequence(self, sequence: SfgSequence) -> set[TypedSymbolOrObject]:
-        """
-        Only in a sequence may parameters be defined and visible to subsequent nodes.
-        """
-
-        params: set[TypedSymbolOrObject] = set()
-
-        def iter_nested_sequences(
-            seq: SfgSequence, visible_params: set[TypedSymbolOrObject]
-        ):
-            for i in range(len(seq.children) - 1, -1, -1):
-                c = seq.children[i]
-
-                if isinstance(c, SfgParamCollectionDeferredNode):
-                    c = c.expand(visible_params=visible_params)
-                    seq[i] = c
-
-                if isinstance(c, SfgSequence):
-                    iter_nested_sequences(c, visible_params)
-                else:
-                    if isinstance(c, SfgStatements):
-                        visible_params -= c.defined_parameters
-
-                    visible_params |= self.visit(c)
-
-        iter_nested_sequences(sequence, params)
-
-        return params
-
-    def branching_node(self, node: SfgCallTreeNode) -> set[TypedSymbolOrObject]:
-        """
-        Each interior node that is not a sequence simply requires the union of all parameters
-        required by its children.
-        """
-        return reduce(lambda x, y: x | y, (self.visit(c) for c in node.children), set())
-
-
-class ParameterCollector:
-    """Collects all parameters required but not defined in a kernel call tree.
-
-    Requires that all sequences in the tree are flattened.
-    """
-
-    @visitor
-    def visit(self, node: SfgCallTreeNode) -> set[TypedSymbolOrObject]:
-        return self.branching_node(node)
-
-    @visit.case(SfgCallTreeLeaf)
-    def leaf(self, leaf: SfgCallTreeLeaf) -> set[TypedSymbolOrObject]:
-        return leaf.required_parameters
-
-    @visit.case(SfgSequence)
-    def sequence(self, sequence: SfgSequence) -> set[TypedSymbolOrObject]:
-        """
-        Only in a sequence may parameters be defined and visible to subsequent nodes.
-        """
-
-        params: set[TypedSymbolOrObject] = set()
-        for c in sequence.children[::-1]:
-            if isinstance(c, SfgStatements):
-                params -= c.defined_parameters
-
-            assert not isinstance(c, SfgSequence), "Sequence not flattened."
-            params |= self.visit(c)
-        return params
-
-    def branching_node(self, node: SfgCallTreeNode) -> set[TypedSymbolOrObject]:
-        """
-        Each interior node that is not a sequence simply requires the union of all parameters
-        required by its children.
-        """
-        return reduce(lambda x, y: x | y, (self.visit(c) for c in node.children), set())
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/compressed_pair.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/compressed_pair.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..25389a2fa5e7be9c2f3bdb35d0a5ff4a746b027a
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/compressed_pair.hpp
@@ -0,0 +1,195 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+#include "trait_backports.hpp"
+
+#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+#  include "no_unique_address.hpp"
+#endif
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+// For no unique address emulation, this is the case taken when neither are empty.
+// For real `[[no_unique_address]]`, this case is always taken.
+template <class _T1, class _T2, class _Enable = void> struct __compressed_pair {
+  _MDSPAN_NO_UNIQUE_ADDRESS _T1 __t1_val{};
+  _MDSPAN_NO_UNIQUE_ADDRESS _T2 __t2_val{};
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept {
+    return __t1_val;
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept {
+    return __t2_val;
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair() = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~__compressed_pair() = default;
+  template <class _T1Like, class _T2Like>
+  MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2)
+      : __t1_val((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {}
+};
+
+#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+
+// First empty.
+template <class _T1, class _T2>
+struct __compressed_pair<
+    _T1, _T2,
+    std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && !_MDSPAN_TRAIT(std::is_empty, _T2)>>
+    : private _T1 {
+  _T2 __t2_val{};
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept {
+    return *static_cast<_T1 *>(this);
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept {
+    return *static_cast<_T1 const *>(this);
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept {
+    return __t2_val;
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair() = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~__compressed_pair() = default;
+  template <class _T1Like, class _T2Like>
+  MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2)
+      : _T1((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {}
+};
+
+// Second empty.
+template <class _T1, class _T2>
+struct __compressed_pair<
+    _T1, _T2,
+    std::enable_if_t<!_MDSPAN_TRAIT(std::is_empty, _T1) && _MDSPAN_TRAIT(std::is_empty, _T2)>>
+    : private _T2 {
+  _T1 __t1_val{};
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept {
+    return __t1_val;
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept {
+    return *static_cast<_T2 *>(this);
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept {
+    return *static_cast<_T2 const *>(this);
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair() = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~__compressed_pair() = default;
+
+  template <class _T1Like, class _T2Like>
+  MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2)
+      : _T2((_T2Like &&) __t2), __t1_val((_T1Like &&) __t1) {}
+};
+
+// Both empty.
+template <class _T1, class _T2>
+struct __compressed_pair<
+    _T1, _T2,
+    std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && _MDSPAN_TRAIT(std::is_empty, _T2)>>
+    // We need to use the __no_unique_address_emulation wrapper here to avoid
+    // base class ambiguities.
+#ifdef _MDSPAN_COMPILER_MSVC
+// MSVC doesn't allow you to access public static member functions of a type
+// when you *happen* to privately inherit from that type.
+    : protected __no_unique_address_emulation<_T1, 0>,
+      protected __no_unique_address_emulation<_T2, 1>
+#else
+    : private __no_unique_address_emulation<_T1, 0>,
+      private __no_unique_address_emulation<_T2, 1>
+#endif
+{
+  using __first_base_t = __no_unique_address_emulation<_T1, 0>;
+  using __second_base_t = __no_unique_address_emulation<_T2, 1>;
+
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept {
+    return this->__first_base_t::__ref();
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept {
+    return this->__first_base_t::__ref();
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept {
+    return this->__second_base_t::__ref();
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept {
+    return this->__second_base_t::__ref();
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair() = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __compressed_pair(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair const &) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair &
+  operator=(__compressed_pair &&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~__compressed_pair() = default;
+  template <class _T1Like, class _T2Like>
+  MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) noexcept
+    : __first_base_t(_T1((_T1Like &&) __t1)),
+      __second_base_t(_T2((_T2Like &&) __t2))
+  { }
+};
+
+#endif // !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+
+} // end namespace detail
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/config.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/config.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..24166462e7abd5e96a941b8ba9f5d302369d07c7
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/config.hpp
@@ -0,0 +1,281 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#ifndef __has_include
+#  define __has_include(x) 0
+#endif
+
+#if __has_include(<version>)
+#  include <version>
+#else
+#  include <type_traits>
+#  include <utility>
+#endif
+
+#ifdef _MSVC_LANG
+#define _MDSPAN_CPLUSPLUS _MSVC_LANG
+#else
+#define _MDSPAN_CPLUSPLUS __cplusplus
+#endif
+
+#define MDSPAN_CXX_STD_14 201402L
+#define MDSPAN_CXX_STD_17 201703L
+#define MDSPAN_CXX_STD_20 202002L
+// Note GCC has not updated this in version 13
+#ifdef __clang__
+#define MDSPAN_CXX_STD_23 202302L
+#else
+#define MDSPAN_CXX_STD_23 202100L
+#endif
+
+#define MDSPAN_HAS_CXX_14 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14)
+#define MDSPAN_HAS_CXX_17 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_17)
+#define MDSPAN_HAS_CXX_20 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_20)
+#define MDSPAN_HAS_CXX_23 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_23)
+
+static_assert(_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later.");
+
+#ifndef _MDSPAN_COMPILER_CLANG
+#  if defined(__clang__)
+#    define _MDSPAN_COMPILER_CLANG __clang__
+#  endif
+#endif
+
+#if !defined(_MDSPAN_COMPILER_MSVC) && !defined(_MDSPAN_COMPILER_MSVC_CLANG)
+#  if defined(_MSC_VER)
+#    if !defined(_MDSPAN_COMPILER_CLANG)
+#      define _MDSPAN_COMPILER_MSVC _MSC_VER
+#    else
+#      define _MDSPAN_COMPILER_MSVC_CLANG _MSC_VER
+#    endif
+#  endif
+#endif
+
+#ifndef _MDSPAN_COMPILER_INTEL
+#  ifdef __INTEL_COMPILER
+#    define _MDSPAN_COMPILER_INTEL __INTEL_COMPILER
+#  endif
+#endif
+
+#ifndef _MDSPAN_COMPILER_APPLECLANG
+#  ifdef __apple_build_version__
+#    define _MDSPAN_COMPILER_APPLECLANG __apple_build_version__
+#  endif
+#endif
+
+#ifndef _MDSPAN_HAS_CUDA
+#  if defined(__CUDACC__)
+#    define _MDSPAN_HAS_CUDA __CUDACC__
+#  endif
+#endif
+
+#ifndef _MDSPAN_HAS_HIP
+#  if defined(__HIPCC__)
+#    define _MDSPAN_HAS_HIP __HIPCC__
+#  endif
+#endif
+
+#ifndef _MDSPAN_HAS_SYCL
+#  if defined(SYCL_LANGUAGE_VERSION)
+#    define _MDSPAN_HAS_SYCL SYCL_LANGUAGE_VERSION
+#  endif
+#endif
+
+#ifndef __has_cpp_attribute
+#  define __has_cpp_attribute(x) 0
+#endif
+
+#ifndef _MDSPAN_PRESERVE_STANDARD_LAYOUT
+// Preserve standard layout by default, but we're not removing the old version
+// that turns this off until we're sure this doesn't have an unreasonable cost
+// to the compiler or optimizer.
+#  define _MDSPAN_PRESERVE_STANDARD_LAYOUT 1
+#endif
+
+#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+#  if ((__has_cpp_attribute(no_unique_address) >= 201803L) && \
+       (!defined(__NVCC__) || MDSPAN_HAS_CXX_20) && \
+       (!defined(_MDSPAN_COMPILER_MSVC) || MDSPAN_HAS_CXX_20))
+#    define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1
+#    define _MDSPAN_NO_UNIQUE_ADDRESS [[no_unique_address]]
+#  else
+#    define _MDSPAN_NO_UNIQUE_ADDRESS
+#  endif
+#endif
+
+// NVCC older than 11.6 chokes on the no-unique-address-emulation
+// so just pretend to use it (to avoid the full blown EBO workaround
+// which NVCC also doesn't like ...), and leave the macro empty
+#ifndef _MDSPAN_NO_UNIQUE_ADDRESS
+#  if defined(__NVCC__)
+#    define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1
+#    define _MDSPAN_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS
+#  endif
+#  define _MDSPAN_NO_UNIQUE_ADDRESS
+#endif
+
+// AMDs HIP compiler seems to have issues with concepts
+// it pretends concepts exist, but doesn't ship <concept>
+#ifndef __HIPCC__
+#ifndef _MDSPAN_USE_CONCEPTS
+#  if defined(__cpp_concepts) && __cpp_concepts >= 201507L
+#    define _MDSPAN_USE_CONCEPTS 1
+#  endif
+#endif
+#endif
+
+#ifndef _MDSPAN_USE_FOLD_EXPRESSIONS
+#  if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \
+          || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17)
+#    define _MDSPAN_USE_FOLD_EXPRESSIONS 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_INLINE_VARIABLES
+#  if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \
+         || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17)
+#    define _MDSPAN_USE_INLINE_VARIABLES 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS
+#  if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \
+          || !MDSPAN_HAS_CXX_17)
+#    if !(defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17)
+#      define _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1
+#    endif
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_VARIABLE_TEMPLATES
+#  if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \
+        || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17)
+#    define _MDSPAN_USE_VARIABLE_TEMPLATES 1
+#  endif
+#endif // _MDSPAN_USE_VARIABLE_TEMPLATES
+
+#ifndef _MDSPAN_USE_CONSTEXPR_14
+#  if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \
+        || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \
+        && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700))
+#    define _MDSPAN_USE_CONSTEXPR_14 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_INTEGER_SEQUENCE
+#  if defined(_MDSPAN_COMPILER_MSVC)
+#    if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304)
+#      define _MDSPAN_USE_INTEGER_SEQUENCE 1
+#    endif
+#  endif
+#endif
+#ifndef _MDSPAN_USE_INTEGER_SEQUENCE
+#  if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \
+        || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \
+        /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \
+        || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__))
+     // several compilers lie about integer_sequence working properly unless the C++14 standard is used
+#    define _MDSPAN_USE_INTEGER_SEQUENCE 1
+#  elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14
+     // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making
+     // integer_sequence work
+#    define _MDSPAN_USE_INTEGER_SEQUENCE 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_RETURN_TYPE_DEDUCTION
+#  if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \
+          || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14)
+#    define _MDSPAN_USE_RETURN_TYPE_DEDUCTION 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+#  if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1170)) && \
+      ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \
+       (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17))
+#    define _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_USE_STANDARD_TRAIT_ALIASES
+#  if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \
+          || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14)
+#    define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1
+#  elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14
+     // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14
+#    define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1
+#  endif
+#endif
+
+#ifndef _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND
+#  ifdef __GNUC__
+#    if __GNUC__ < 9
+#      define _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1
+#    endif
+#  endif
+#endif
+
+#ifndef MDSPAN_CONDITIONAL_EXPLICIT
+#  if MDSPAN_HAS_CXX_20
+#    define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND)
+#  else
+#    define MDSPAN_CONDITIONAL_EXPLICIT(COND)
+#  endif
+#endif
+
+#ifndef MDSPAN_USE_BRACKET_OPERATOR
+#  if defined(__cpp_multidimensional_subscript)
+#    define MDSPAN_USE_BRACKET_OPERATOR 1
+#  else
+#    define MDSPAN_USE_BRACKET_OPERATOR 0
+#  endif
+#endif
+
+#ifndef MDSPAN_USE_PAREN_OPERATOR
+#  if !MDSPAN_USE_BRACKET_OPERATOR
+#    define MDSPAN_USE_PAREN_OPERATOR 1
+#  else
+#    define MDSPAN_USE_PAREN_OPERATOR 0
+#  endif
+#endif
+
+#if MDSPAN_USE_BRACKET_OPERATOR
+#  define __MDSPAN_OP(mds,...) mds[__VA_ARGS__]
+// Corentins demo compiler for subscript chokes on empty [] call,
+// though I believe the proposal supports it?
+#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR
+#  define __MDSPAN_OP0(mds) mds.accessor().access(mds.data_handle(),0)
+#else
+#  define __MDSPAN_OP0(mds) mds[]
+#endif
+#  define __MDSPAN_OP1(mds, a) mds[a]
+#  define __MDSPAN_OP2(mds, a, b) mds[a,b]
+#  define __MDSPAN_OP3(mds, a, b, c) mds[a,b,c]
+#  define __MDSPAN_OP4(mds, a, b, c, d) mds[a,b,c,d]
+#  define __MDSPAN_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e]
+#  define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f]
+#else
+#  define __MDSPAN_OP(mds,...) mds(__VA_ARGS__)
+#  define __MDSPAN_OP0(mds) mds()
+#  define __MDSPAN_OP1(mds, a) mds(a)
+#  define __MDSPAN_OP2(mds, a, b) mds(a,b)
+#  define __MDSPAN_OP3(mds, a, b, c) mds(a,b,c)
+#  define __MDSPAN_OP4(mds, a, b, c, d) mds(a,b,c,d)
+#  define __MDSPAN_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e)
+#  define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f)
+#endif
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/default_accessor.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/default_accessor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea0f537b2fe191ace9c6f5271d5b7331e172a4fd
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/default_accessor.hpp
@@ -0,0 +1,56 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+
+#include <cstddef> // size_t
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+template <class ElementType>
+struct default_accessor {
+
+  using offset_policy = default_accessor;
+  using element_type = ElementType;
+  using reference = ElementType&;
+  using data_handle_type = ElementType*;
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default;
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[])
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr default_accessor(default_accessor<OtherElementType>) noexcept {}
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr data_handle_type
+  offset(data_handle_type p, size_t i) const noexcept {
+    return p + i;
+  }
+
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference access(data_handle_type p, size_t i) const noexcept {
+    return p[i];
+  }
+
+};
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e29da13d6adfd1107fe7ea3ff022bfcf376f189
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp
@@ -0,0 +1,35 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+
+#if defined(__cpp_lib_span)
+#include <span>
+#endif
+
+#include <cstddef>  // size_t
+#include <limits>   // numeric_limits
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+#if defined(__cpp_lib_span)
+using std::dynamic_extent;
+#else
+_MDSPAN_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits<size_t>::max();
+#endif
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+//==============================================================================================================
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/extents.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/extents.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..98a57e34e649bb6e026df34af073531883a3abbf
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/extents.hpp
@@ -0,0 +1,691 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+#include "dynamic_extent.hpp"
+#include "utility.hpp"
+
+#ifdef __cpp_lib_span
+#include <span>
+#endif
+#include <array>
+#include <type_traits>
+
+#include <cassert>
+#include <cinttypes>
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+// Function used to check compatibility of extents in converting constructor
+// can't be a private member function for some reason.
+template <size_t... Extents, size_t... OtherExtents>
+static constexpr std::integral_constant<bool, false> __check_compatible_extents(
+    std::integral_constant<bool, false>,
+    std::integer_sequence<size_t, Extents...>,
+    std::integer_sequence<size_t, OtherExtents...>) noexcept {
+  return {};
+}
+
+// This helper prevents ICE's on MSVC.
+template <size_t Lhs, size_t Rhs>
+struct __compare_extent_compatible : std::integral_constant<bool,
+     Lhs == dynamic_extent ||
+     Rhs == dynamic_extent ||
+     Lhs == Rhs>
+{};
+
+template <size_t... Extents, size_t... OtherExtents>
+static constexpr std::integral_constant<
+    bool, _MDSPAN_FOLD_AND(__compare_extent_compatible<Extents, OtherExtents>::value)>
+__check_compatible_extents(
+    std::integral_constant<bool, true>,
+    std::integer_sequence<size_t, Extents...>,
+    std::integer_sequence<size_t, OtherExtents...>) noexcept {
+  return {};
+}
+
+template<class IndexType, class ... Arguments>
+MDSPAN_INLINE_FUNCTION
+static constexpr bool are_valid_indices() {
+    return
+      _MDSPAN_FOLD_AND(std::is_convertible<Arguments, IndexType>::value) &&
+      _MDSPAN_FOLD_AND(std::is_nothrow_constructible<IndexType, Arguments>::value);
+}
+
+// ------------------------------------------------------------------
+// ------------ static_array ----------------------------------------
+// ------------------------------------------------------------------
+
+// array like class which provides an array of static values with get
+// function and operator [].
+
+// Implementation of Static Array with recursive implementation of get.
+template <size_t R, class T, T... Extents> struct static_array_impl;
+
+template <size_t R, class T, T FirstExt, T... Extents>
+struct static_array_impl<R, T, FirstExt, Extents...> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr static T get(size_t r) {
+    if (r == R)
+      return FirstExt;
+    else
+      return static_array_impl<R + 1, T, Extents...>::get(r);
+  }
+  template <size_t r> MDSPAN_INLINE_FUNCTION constexpr static T get() {
+#if MDSPAN_HAS_CXX_17
+    if constexpr (r == R)
+      return FirstExt;
+    else
+      return static_array_impl<R + 1, T, Extents...>::template get<r>();
+#else
+    get(r);
+#endif
+  }
+};
+
+// End the recursion
+template <size_t R, class T, T FirstExt>
+struct static_array_impl<R, T, FirstExt> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr static T get(size_t) { return FirstExt; }
+  template <size_t> MDSPAN_INLINE_FUNCTION constexpr static T get() {
+    return FirstExt;
+  }
+};
+
+// Don't start recursion if size 0
+template <class T> struct static_array_impl<0, T> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr static T get(size_t) { return T(); }
+  template <size_t> MDSPAN_INLINE_FUNCTION constexpr static T get() {
+    return T();
+  }
+};
+
+// Static array, provides get<r>(), get(r) and operator[r]
+template <class T, T... Values> struct static_array:
+  public static_array_impl<0, T, Values...>  {
+
+public:
+  using value_type = T;
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t size() { return sizeof...(Values); }
+};
+
+
+// ------------------------------------------------------------------
+// ------------ index_sequence_scan ---------------------------------
+// ------------------------------------------------------------------
+
+// index_sequence_scan takes compile time values and provides get(r)
+// and get<r>() which return the sum of the first r-1 values.
+
+// Recursive implementation for get
+template <size_t R, size_t... Values> struct index_sequence_scan_impl;
+
+template <size_t R, size_t FirstVal, size_t... Values>
+struct index_sequence_scan_impl<R, FirstVal, Values...> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t get(size_t r) {
+    if (r > R)
+      return FirstVal + index_sequence_scan_impl<R + 1, Values...>::get(r);
+    else
+      return 0;
+  }
+};
+
+template <size_t R, size_t FirstVal>
+struct index_sequence_scan_impl<R, FirstVal> {
+#if defined(__NVCC__) || defined(__NVCOMPILER) ||                              \
+    defined(_MDSPAN_COMPILER_INTEL)
+  // NVCC warns about pointless comparison with 0 for R==0 and r being const
+  // evaluatable and also 0.
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t get(size_t r) {
+    return static_cast<int64_t>(R) > static_cast<int64_t>(r) ? FirstVal : 0;
+  }
+#else
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; }
+#endif
+};
+template <> struct index_sequence_scan_impl<0> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t get(size_t) { return 0; }
+};
+
+// ------------------------------------------------------------------
+// ------------ possibly_empty_array  -------------------------------
+// ------------------------------------------------------------------
+
+// array like class which provides get function and operator [], and
+// has a specialization for the size 0 case.
+// This is needed to make the maybe_static_array be truly empty, for
+// all static values.
+
+template <class T, size_t N> struct possibly_empty_array {
+  T vals[N]{};
+  MDSPAN_INLINE_FUNCTION
+  constexpr T &operator[](size_t r) { return vals[r]; }
+  MDSPAN_INLINE_FUNCTION
+  constexpr const T &operator[](size_t r) const { return vals[r]; }
+};
+
+template <class T> struct possibly_empty_array<T, 0> {
+  MDSPAN_INLINE_FUNCTION
+  constexpr T operator[](size_t) { return T(); }
+  MDSPAN_INLINE_FUNCTION
+  constexpr const T operator[](size_t) const { return T(); }
+};
+
+// ------------------------------------------------------------------
+// ------------ maybe_static_array ----------------------------------
+// ------------------------------------------------------------------
+
+// array like class which has a mix of static and runtime values but
+// only stores the runtime values.
+// The type of the static and the runtime values can be different.
+// The position of a dynamic value is indicated through a tag value.
+template <class TDynamic, class TStatic, TStatic dyn_tag, TStatic... Values>
+struct maybe_static_array {
+
+  static_assert(std::is_convertible<TStatic, TDynamic>::value, "maybe_static_array: TStatic must be convertible to TDynamic");
+  static_assert(std::is_convertible<TDynamic, TStatic>::value, "maybe_static_array: TDynamic must be convertible to TStatic");
+
+private:
+  // Static values member
+  using static_vals_t = static_array<TStatic, Values...>;
+  constexpr static size_t m_size = sizeof...(Values);
+  constexpr static size_t m_size_dynamic =
+      _MDSPAN_FOLD_PLUS_RIGHT((Values == dyn_tag), 0);
+
+  // Dynamic values member
+  _MDSPAN_NO_UNIQUE_ADDRESS possibly_empty_array<TDynamic, m_size_dynamic>
+      m_dyn_vals;
+
+  // static mapping of indices to the position in the dynamic values array
+  using dyn_map_t = index_sequence_scan_impl<0, static_cast<size_t>(Values == dyn_tag)...>;
+public:
+
+  // two types for static and dynamic values
+  using value_type = TDynamic;
+  using static_value_type = TStatic;
+  // tag value indicating dynamic value
+  constexpr static static_value_type tag_value = dyn_tag;
+
+  constexpr maybe_static_array() = default;
+
+  // constructor for all static values
+  // TODO: add precondition check?
+  MDSPAN_TEMPLATE_REQUIRES(class... Vals,
+                           /* requires */ ((m_size_dynamic == 0) &&
+                                           (sizeof...(Vals) > 0)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(Vals...) : m_dyn_vals{} {}
+
+  // constructors from dynamic values only
+  MDSPAN_TEMPLATE_REQUIRES(class... DynVals,
+                           /* requires */ (sizeof...(DynVals) ==
+                                               m_size_dynamic &&
+                                           m_size_dynamic > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(DynVals... vals)
+      : m_dyn_vals{static_cast<TDynamic>(vals)...} {}
+
+
+  MDSPAN_TEMPLATE_REQUIRES(class T, size_t N,
+                           /* requires */ (N == m_size_dynamic && N > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::array<T, N> &vals) {
+    for (size_t r = 0; r < N; r++)
+      m_dyn_vals[r] = static_cast<TDynamic>(vals[r]);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(class T, size_t N,
+                           /* requires */ (N == m_size_dynamic && N == 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::array<T, N> &) : m_dyn_vals{} {}
+
+#ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(class T, size_t N,
+                           /* requires */ (N == m_size_dynamic && N > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::span<T, N> &vals) {
+    for (size_t r = 0; r < N; r++)
+      m_dyn_vals[r] = static_cast<TDynamic>(vals[r]);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(class T, size_t N,
+                           /* requires */ (N == m_size_dynamic && N == 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::span<T, N> &) : m_dyn_vals{} {}
+#endif
+
+  // constructors from all values
+  MDSPAN_TEMPLATE_REQUIRES(class... DynVals,
+                           /* requires */ (sizeof...(DynVals) !=
+                                               m_size_dynamic &&
+                                           m_size_dynamic > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(DynVals... vals)
+    : m_dyn_vals{} {
+    static_assert((sizeof...(DynVals) == m_size), "Invalid number of values.");
+    TDynamic values[m_size]{static_cast<TDynamic>(vals)...};
+    for (size_t r = 0; r < m_size; r++) {
+      TStatic static_val = static_vals_t::get(r);
+      if (static_val == dyn_tag) {
+        m_dyn_vals[dyn_map_t::get(r)] = values[r];
+      }
+// Precondition check
+#ifdef _MDSPAN_DEBUG
+      else {
+        assert(values[r] == static_cast<TDynamic>(static_val));
+      }
+#endif
+    }
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+      class T, size_t N,
+      /* requires */ (N != m_size_dynamic && m_size_dynamic > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::array<T, N> &vals) {
+    static_assert((N == m_size), "Invalid number of values.");
+// Precondition check
+#ifdef _MDSPAN_DEBUG
+    assert(N == m_size);
+#endif
+    for (size_t r = 0; r < m_size; r++) {
+      TStatic static_val = static_vals_t::get(r);
+      if (static_val == dyn_tag) {
+        m_dyn_vals[dyn_map_t::get(r)] = static_cast<TDynamic>(vals[r]);
+      }
+// Precondition check
+#ifdef _MDSPAN_DEBUG
+      else {
+        assert(static_cast<TDynamic>(vals[r]) ==
+               static_cast<TDynamic>(static_val));
+      }
+#endif
+    }
+  }
+
+#ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(
+      class T, size_t N,
+      /* requires */ (N != m_size_dynamic && m_size_dynamic > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr maybe_static_array(const std::span<T, N> &vals) {
+    static_assert((N == m_size) || (m_size == dynamic_extent));
+#ifdef _MDSPAN_DEBUG
+    assert(N == m_size);
+#endif
+    for (size_t r = 0; r < m_size; r++) {
+      TStatic static_val = static_vals_t::get(r);
+      if (static_val == dyn_tag) {
+        m_dyn_vals[dyn_map_t::get(r)] = static_cast<TDynamic>(vals[r]);
+      }
+#ifdef _MDSPAN_DEBUG
+      else {
+        assert(static_cast<TDynamic>(vals[r]) ==
+               static_cast<TDynamic>(static_val));
+      }
+#endif
+    }
+  }
+#endif
+
+  // access functions
+  MDSPAN_INLINE_FUNCTION
+  constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr TDynamic value(size_t r) const {
+    TStatic static_val = static_vals_t::get(r);
+    return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)]
+                                        : static_cast<TDynamic>(static_val);
+  }
+  MDSPAN_INLINE_FUNCTION
+  constexpr TDynamic operator[](size_t r) const { return value(r); }
+
+
+  // observers
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t size() { return m_size; }
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t size_dynamic() { return m_size_dynamic; }
+};
+
+} // namespace detail
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+// ------------------------------------------------------------------
+// ------------ extents ---------------------------------------------
+// ------------------------------------------------------------------
+
+// Class to describe the extents of a multi dimensional array.
+// Used by mdspan, mdarray and layout mappings.
+// See ISO C++ standard [mdspan.extents]
+
+template <class IndexType, size_t... Extents> class extents {
+public:
+  // typedefs for integral types used
+  using index_type = IndexType;
+  using size_type = std::make_unsigned_t<index_type>;
+  using rank_type = size_t;
+
+  static_assert(std::is_integral<index_type>::value && !std::is_same<index_type, bool>::value,
+                MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type");
+private:
+  constexpr static rank_type m_rank = sizeof...(Extents);
+  constexpr static rank_type m_rank_dynamic =
+      _MDSPAN_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0);
+
+  // internal storage type using maybe_static_array
+  using vals_t =
+      detail::maybe_static_array<IndexType, size_t, dynamic_extent, Extents...>;
+  _MDSPAN_NO_UNIQUE_ADDRESS vals_t m_vals;
+
+public:
+  // [mdspan.extents.obs], observers of multidimensional index space
+  MDSPAN_INLINE_FUNCTION
+  constexpr static rank_type rank() noexcept { return m_rank; }
+  MDSPAN_INLINE_FUNCTION
+  constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); }
+  MDSPAN_INLINE_FUNCTION
+  constexpr static size_t static_extent(rank_type r) noexcept {
+    return vals_t::static_value(r);
+  }
+
+  // [mdspan.extents.cons], constructors
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr extents() noexcept = default;
+
+  // Construction from just dynamic or all values.
+  // Precondition check is deferred to maybe_static_array constructor
+  MDSPAN_TEMPLATE_REQUIRES(
+      class... OtherIndexTypes,
+      /* requires */ (
+          _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, OtherIndexTypes,
+                                         index_type) /* && ... */) &&
+          _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type,
+                                         OtherIndexTypes) /* && ... */) &&
+          (sizeof...(OtherIndexTypes) == m_rank ||
+           sizeof...(OtherIndexTypes) == m_rank_dynamic)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr explicit extents(OtherIndexTypes... dynvals) noexcept
+      : m_vals(static_cast<index_type>(dynvals)...) {}
+
+  MDSPAN_TEMPLATE_REQUIRES(
+      class OtherIndexType, size_t N,
+      /* requires */
+      (
+          _MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) &&
+          _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type,
+              const OtherIndexType&) &&
+          (N == m_rank || N == m_rank_dynamic)))
+  MDSPAN_INLINE_FUNCTION
+  MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic)
+  constexpr extents(const std::array<OtherIndexType, N> &exts) noexcept
+      : m_vals(std::move(exts)) {}
+
+#ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(
+      class OtherIndexType, size_t N,
+      /* requires */
+      (_MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) &&
+       _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const OtherIndexType&) &&
+       (N == m_rank || N == m_rank_dynamic)))
+  MDSPAN_INLINE_FUNCTION
+  MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic)
+  constexpr extents(const std::span<OtherIndexType, N> &exts) noexcept
+      : m_vals(std::move(exts)) {}
+#endif
+
+private:
+  // Function to construct extents storage from other extents.
+  // With C++ 17 the first two variants could be collapsed using if constexpr
+  // in which case you don't need all the requires clauses.
+  // in C++ 14 mode that doesn't work due to infinite recursion
+  MDSPAN_TEMPLATE_REQUIRES(
+      size_t DynCount, size_t R, class OtherExtents, class... DynamicValues,
+      /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr
+  vals_t __construct_vals_from_extents(std::integral_constant<size_t, DynCount>,
+                                       std::integral_constant<size_t, R>,
+                                       const OtherExtents &exts,
+                                       DynamicValues... dynamic_values) noexcept {
+    return __construct_vals_from_extents(
+        std::integral_constant<size_t, DynCount + 1>(),
+        std::integral_constant<size_t, R + 1>(), exts, dynamic_values...,
+        exts.extent(R));
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+      size_t DynCount, size_t R, class OtherExtents, class... DynamicValues,
+      /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr
+  vals_t __construct_vals_from_extents(std::integral_constant<size_t, DynCount>,
+                                       std::integral_constant<size_t, R>,
+                                       const OtherExtents &exts,
+                                       DynamicValues... dynamic_values) noexcept {
+    return __construct_vals_from_extents(
+        std::integral_constant<size_t, DynCount>(),
+        std::integral_constant<size_t, R + 1>(), exts, dynamic_values...);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+      size_t DynCount, size_t R, class OtherExtents, class... DynamicValues,
+      /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr
+  vals_t __construct_vals_from_extents(std::integral_constant<size_t, DynCount>,
+                                       std::integral_constant<size_t, R>,
+                                       const OtherExtents &,
+                                       DynamicValues... dynamic_values) noexcept {
+    return vals_t{static_cast<index_type>(dynamic_values)...};
+  }
+
+public:
+
+  // Converting constructor from other extents specializations
+    MDSPAN_TEMPLATE_REQUIRES(
+        class OtherIndexType, size_t... OtherExtents,
+        /* requires */
+        (
+            /* multi-stage check to protect from invalid pack expansion when sizes
+            don't match? */
+            decltype(detail::__check_compatible_extents(
+              // using: sizeof...(Extents) == sizeof...(OtherExtents) as the second argument fails with MSVC+NVCC with some obscure expansion error
+              // MSVC: 19.38.33133 NVCC: 12.0
+              std::integral_constant<bool, extents<int, Extents...>::rank() == extents<int, OtherExtents...>::rank()>{},
+              std::integer_sequence<size_t, Extents...>{},
+              std::integer_sequence<size_t, OtherExtents...>{}))::value
+      )
+  )
+  MDSPAN_INLINE_FUNCTION
+  MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) &&
+                                (OtherExtents == dynamic_extent)) ||
+                               ...) ||
+                              (std::numeric_limits<index_type>::max() <
+                               std::numeric_limits<OtherIndexType>::max()))
+  constexpr extents(const extents<OtherIndexType, OtherExtents...> &other) noexcept
+      : m_vals(__construct_vals_from_extents(
+            std::integral_constant<size_t, 0>(),
+            std::integral_constant<size_t, 0>(), other)) {}
+
+  // Comparison operator
+  template <class OtherIndexType, size_t... OtherExtents>
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator==(const extents &lhs,
+             const extents<OtherIndexType, OtherExtents...> &rhs) noexcept {
+    return
+      rank() == extents<OtherIndexType, OtherExtents...>::rank() &&
+      detail::rankwise_equal(detail::with_rank<rank()>{}, rhs, lhs, detail::extent);
+  }
+
+#if !(MDSPAN_HAS_CXX_20)
+  template <class OtherIndexType, size_t... OtherExtents>
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator!=(extents const &lhs,
+             extents<OtherIndexType, OtherExtents...> const &rhs) noexcept {
+    return !(lhs == rhs);
+  }
+#endif
+};
+
+// Recursive helper classes to implement dextents alias for extents
+namespace detail {
+
+template <class IndexType, size_t Rank,
+          class Extents = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType>>
+struct __make_dextents;
+
+template <class IndexType, size_t Rank, size_t... ExtentsPack>
+struct __make_dextents<
+    IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType, ExtentsPack...>>
+{
+  using type = typename __make_dextents<
+      IndexType, Rank - 1,
+      ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType,
+                                                ::MDSPAN_IMPL_STANDARD_NAMESPACE::dynamic_extent,
+                                                ExtentsPack...>>::type;
+};
+
+template <class IndexType, size_t... ExtentsPack>
+struct __make_dextents<
+    IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType, ExtentsPack...>>
+{
+  using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType, ExtentsPack...>;
+};
+
+} // end namespace detail
+
+// [mdspan.extents.dextents], alias template
+template <class IndexType, size_t Rank>
+using dextents = typename detail::__make_dextents<IndexType, Rank>::type;
+
+// Deduction guide for extents
+#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION)
+template <class... IndexTypes>
+extents(IndexTypes...)
+    -> extents<size_t,
+               ((void) sizeof(IndexTypes), ::MDSPAN_IMPL_STANDARD_NAMESPACE::dynamic_extent)...>;
+#endif
+
+// Helper type traits for identifying a class as extents.
+namespace detail {
+
+template <class T> struct __is_extents : ::std::false_type {};
+
+template <class IndexType, size_t... ExtentsPack>
+struct __is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<IndexType, ExtentsPack...>>
+    : ::std::true_type {};
+
+template <class T>
+#if MDSPAN_HAS_CXX_17
+inline
+#else
+static
+#endif
+constexpr bool __is_extents_v = __is_extents<T>::value;
+
+template<class InputIndexType, class ExtentsIndexType>
+MDSPAN_INLINE_FUNCTION
+constexpr void
+check_lower_bound(InputIndexType user_index,
+                  ExtentsIndexType /* current_extent */,
+                  std::true_type /* is_signed */)
+{
+  (void) user_index; // prevent unused variable warning
+#ifdef _MDSPAN_DEBUG
+  assert(static_cast<ExtentsIndexType>(user_index) >= 0);
+#endif
+}
+
+template<class InputIndexType, class ExtentsIndexType>
+MDSPAN_INLINE_FUNCTION
+constexpr void
+check_lower_bound(InputIndexType /* user_index */,
+                  ExtentsIndexType /* current_extent */,
+                  std::false_type /* is_signed */)
+{}
+
+template<class InputIndexType, class ExtentsIndexType>
+MDSPAN_INLINE_FUNCTION
+constexpr void
+check_upper_bound(InputIndexType user_index,
+                  ExtentsIndexType current_extent)
+{
+  (void) user_index; // prevent unused variable warnings
+  (void) current_extent;
+#ifdef _MDSPAN_DEBUG
+  assert(static_cast<ExtentsIndexType>(user_index) < current_extent);
+#endif
+}
+
+// Returning true to use AND fold instead of comma
+// CPP14 mode doesn't like the use of void expressions
+// with the way the _MDSPAN_FOLD_AND is set up
+template<class InputIndex, class ExtentsIndexType>
+MDSPAN_INLINE_FUNCTION
+constexpr bool
+check_one_index(InputIndex user_index,
+                ExtentsIndexType current_extent)
+{
+  check_lower_bound(user_index, current_extent,
+    std::integral_constant<bool, std::is_signed<ExtentsIndexType>::value>{});
+  check_upper_bound(user_index, current_extent);
+  return true;
+}
+
+template<size_t ... RankIndices,
+         class ExtentsIndexType, size_t ... Exts,
+         class ... Indices>
+MDSPAN_INLINE_FUNCTION
+constexpr void
+check_all_indices_helper(std::index_sequence<RankIndices...>,
+                         const extents<ExtentsIndexType, Exts...>& exts,
+                         Indices... indices)
+{
+  // Suppress warning about statement has no effect
+  (void) _MDSPAN_FOLD_AND(
+    (check_one_index(indices, exts.extent(RankIndices)))
+  );
+}
+
+template<class ExtentsIndexType, size_t ... Exts,
+         class ... Indices>
+MDSPAN_INLINE_FUNCTION
+constexpr void
+check_all_indices(const extents<ExtentsIndexType, Exts...>& exts,
+                  Indices... indices)
+{
+  check_all_indices_helper(std::make_index_sequence<sizeof...(Indices)>(),
+                           exts, indices...);
+}
+
+} // namespace detail
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/full_extent_t.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/full_extent_t.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bd4b5c6a8baa31a21281b1528ae2f57a264a7d53
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/full_extent_t.hpp
@@ -0,0 +1,26 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+struct full_extent_t { explicit full_extent_t() = default; };
+
+_MDSPAN_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ };
+
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_left.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_left.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..222fba7aa04951df4f73b7894db14b6ab34233c3
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_left.hpp
@@ -0,0 +1,266 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+#include "trait_backports.hpp"
+#include "extents.hpp"
+#include "layout_stride.hpp"
+#include "utility.hpp"
+#if MDSPAN_HAS_CXX_17
+#include "../__p2642_bits/layout_padded_fwd.hpp"
+#endif
+#include <type_traits>
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+//==============================================================================
+
+template <class Extents>
+class layout_left::mapping {
+  public:
+    using extents_type = Extents;
+    using index_type = typename extents_type::index_type;
+    using size_type = typename extents_type::size_type;
+    using rank_type = typename extents_type::rank_type;
+    using layout_type = layout_left;
+  private:
+
+    static_assert(detail::__is_extents_v<extents_type>,
+                  MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents.");
+
+    template <class>
+    friend class mapping;
+
+    // i0+(i1 + E(1)*(i2 + E(2)*i3))
+    template <size_t r, size_t Rank>
+    struct __rank_count {};
+
+    template <size_t r, size_t Rank, class I, class... Indices>
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(
+      __rank_count<r,Rank>, const I& i, Indices... idx) const {
+      return __compute_offset(__rank_count<r+1,Rank>(), idx...) *
+                 __extents.extent(r) + i;
+    }
+
+    template<class I>
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(
+      __rank_count<extents_type::rank()-1,extents_type::rank()>, const I& i) const {
+      return i;
+    }
+
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; }
+
+  public:
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default;
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default;
+
+    _MDSPAN_HOST_DEVICE
+    constexpr mapping(extents_type const& __exts) noexcept
+      :__extents(__exts)
+    { }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible<OtherExtents, extents_type>::value)) // needs two () due to comma
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) &&
+        (extents_type::rank() <= 1)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible<OtherExtents, extents_type>::value)) // needs two () due to comma
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(layout_right::mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+    }
+
+#if MDSPAN_HAS_CXX_17
+    /**
+     * Converting constructor from `layout_left_padded::mapping`.
+     *
+     * This overload participates in overload resolution only if _Mapping is a layout_left_padded mapping and
+     * extents_type is constructible from _Mapping::extents_type.
+     *
+     * \note There is currently a difference from p2642r2, where this function is specified as taking
+     * `layout_left_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible.
+     */
+    MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (
+        MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_left_padded_mapping<_Mapping>::value
+        && std::is_constructible_v<extents_type, typename _Mapping::extents_type>
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v<typename _Mapping::extents_type, extents_type>))
+    mapping(const _Mapping& __other) noexcept
+      : __extents(__other.extents())
+    {
+      MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::
+          check_padded_layout_converting_constructor_mandates<
+            extents_type, _Mapping>(detail::with_rank<extents_type::rank()>{});
+      MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::
+          check_padded_layout_converting_constructor_preconditions<
+              extents_type>(detail::with_rank<extents_type::rank()>{}, __other);
+    }
+#endif
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0))
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(layout_stride::mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+       detail::validate_strides(detail::with_rank<extents_type::rank()>{}, layout_left{}, __extents, other);
+    }
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default;
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr const extents_type& extents() const noexcept {
+      return __extents;
+    }
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type required_span_size() const noexcept {
+      index_type value = 1;
+      for(rank_type r=0; r<extents_type::rank(); r++) value*=__extents.extent(r);
+      return value;
+    }
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class... Indices,
+      /* requires */ (
+        (sizeof...(Indices) == extents_type::rank()) &&
+        (detail::are_valid_indices<index_type, Indices...>())
+      )
+    )
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type operator()(Indices... idxs) const noexcept {
+#if ! defined(NDEBUG)
+      detail::check_all_indices(this->extents(), idxs...);
+#endif // ! NDEBUG
+      return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast<index_type>(idxs)...);
+    }
+
+
+
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; }
+
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; }
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type stride(rank_type i) const noexcept
+#if MDSPAN_HAS_CXX_20
+      requires ( Extents::rank() > 0 )
+#endif
+    {
+      index_type value = 1;
+      for(rank_type r=0; r<i; r++) value*=__extents.extent(r);
+      return value;
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ ( Extents::rank() == OtherExtents::rank())
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator==(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return lhs.extents() == rhs.extents();
+    }
+
+    // In C++ 20 the not equal exists if equal is found
+#if !(MDSPAN_HAS_CXX_20)
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ ( Extents::rank() == OtherExtents::rank())
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator!=(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return lhs.extents() != rhs.extents();
+    }
+#endif
+
+    // Not really public, but currently needed to implement fully constexpr useable submdspan:
+    template<size_t N, class SizeType, size_t ... E, size_t ... Idx>
+    constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents<SizeType, E...>,std::integer_sequence<size_t, Idx...>) const {
+      return _MDSPAN_FOLD_TIMES_RIGHT((Idx<N? __extents.template __extent<Idx>():1),1);
+    }
+    template<size_t N>
+    constexpr index_type __stride() const noexcept {
+      return __get_stride<N>(__extents, std::make_index_sequence<extents_type::rank()>());
+    }
+
+private:
+   _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{};
+
+   // [mdspan.submdspan.mapping], submdspan mapping specialization
+   template<class... SliceSpecifiers>
+    MDSPAN_INLINE_FUNCTION
+    constexpr auto submdspan_mapping_impl(
+       SliceSpecifiers... slices) const;
+
+   template<class... SliceSpecifiers>
+     friend constexpr auto submdspan_mapping(
+       const mapping& src, SliceSpecifiers... slices) {
+         return src.submdspan_mapping_impl(slices...);
+     }
+};
+
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_right.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_right.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..284569f6533251667d07a8b98de2feb01bbd9381
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_right.hpp
@@ -0,0 +1,262 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+#include "trait_backports.hpp"
+#include "extents.hpp"
+#include "layout_stride.hpp"
+#include "utility.hpp"
+#if MDSPAN_HAS_CXX_17
+#include "../__p2642_bits/layout_padded_fwd.hpp"
+#endif
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+//==============================================================================
+template <class Extents>
+class layout_right::mapping {
+  public:
+    using extents_type = Extents;
+    using index_type = typename extents_type::index_type;
+    using size_type = typename extents_type::size_type;
+    using rank_type = typename extents_type::rank_type;
+    using layout_type = layout_right;
+  private:
+
+    static_assert(detail::__is_extents_v<extents_type>,
+                  MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents.");
+
+    template <class>
+    friend class mapping;
+
+    // i0+(i1 + E(1)*(i2 + E(2)*i3))
+    template <size_t r, size_t Rank>
+    struct __rank_count {};
+
+    template <size_t r, size_t Rank, class I, class... Indices>
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(
+      index_type offset, __rank_count<r,Rank>, const I& i, Indices... idx) const {
+      return __compute_offset(offset * __extents.extent(r) + i,__rank_count<r+1,Rank>(),  idx...);
+    }
+
+    template<class I, class ... Indices>
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(
+      __rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const {
+      return __compute_offset(i,__rank_count<1,extents_type::rank()>(),idx...);
+    }
+
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(size_t offset, __rank_count<extents_type::rank(), extents_type::rank()>) const {
+      return static_cast<index_type>(offset);
+    }
+
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; }
+
+  public:
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default;
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default;
+
+    _MDSPAN_HOST_DEVICE
+    constexpr mapping(extents_type const& __exts) noexcept
+      :__extents(__exts)
+    { }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible<OtherExtents, extents_type>::value)) // needs two () due to comma
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) &&
+        (extents_type::rank() <= 1)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible<OtherExtents, extents_type>::value)) // needs two () due to comma
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(layout_left::mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+    }
+
+    /**
+     * Converting constructor from `layout_right_padded::mapping`.
+     *
+     * This overload participates in overload resolution only if _Mapping is a layout_right_padded mapping and
+     * extents_type is constructible from _Mapping::extents_type.
+     *
+     * \note There is currently a difference from p2642r2, where this function is specified as taking
+     * `layout_right_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible.
+     */
+#if MDSPAN_HAS_CXX_17
+    MDSPAN_TEMPLATE_REQUIRES(
+        class _Mapping,
+        /* requires */ (
+        MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_right_padded_mapping<_Mapping>::value
+        && std::is_constructible_v<extents_type, typename _Mapping::extents_type>))
+    MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v<typename _Mapping::extents_type, extents_type>))
+    mapping(const _Mapping &__other) noexcept
+        : __extents(__other.extents())
+    {
+      MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::
+          check_padded_layout_converting_constructor_mandates<
+            extents_type, _Mapping>(detail::with_rank<extents_type::rank()>{});
+      MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::
+          check_padded_layout_converting_constructor_preconditions<
+            extents_type>(detail::with_rank<extents_type::rank()>{}, __other);
+    }
+#endif
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents)
+      )
+    )
+    MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0))
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(layout_stride::mapping<OtherExtents> const& other) noexcept // NOLINT(google-explicit-constructor)
+      :__extents(other.extents())
+    {
+       /*
+        * TODO: check precondition
+        * other.required_span_size() is a representable value of type index_type
+        */
+       detail::validate_strides(detail::with_rank<extents_type::rank()>{}, layout_right{}, __extents, other);
+    }
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default;
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr const extents_type& extents() const noexcept {
+      return __extents;
+    }
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type required_span_size() const noexcept {
+      index_type value = 1;
+      for(rank_type r=0; r != extents_type::rank(); ++r) value*=__extents.extent(r);
+      return value;
+    }
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class ... Indices,
+      /* requires */ (
+      (sizeof...(Indices) == extents_type::rank()) &&
+      (detail::are_valid_indices<index_type, Indices...>())
+      )
+    )
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type operator()(Indices... idxs) const noexcept {
+#if ! defined(NDEBUG)
+      detail::check_all_indices(this->extents(), idxs...);
+#endif // ! NDEBUG
+      return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast<index_type>(idxs)...);
+    }
+
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; }
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type stride(rank_type i) const noexcept
+#if MDSPAN_HAS_CXX_20
+      requires ( Extents::rank() > 0 )
+#endif
+    {
+      index_type value = 1;
+      for(rank_type r=extents_type::rank()-1; r>i; r--) value*=__extents.extent(r);
+      return value;
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ ( Extents::rank() == OtherExtents::rank())
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator==(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return lhs.extents() == rhs.extents();
+    }
+
+    // In C++ 20 the not equal exists if equal is found
+#if !(MDSPAN_HAS_CXX_20)
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (Extents::rank() == OtherExtents::rank())
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator!=(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return lhs.extents() != rhs.extents();
+    }
+#endif
+
+    // Not really public, but currently needed to implement fully constexpr useable submdspan:
+    template<size_t N, class SizeType, size_t ... E, size_t ... Idx>
+    constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents<SizeType, E...>,std::integer_sequence<size_t, Idx...>) const {
+      return _MDSPAN_FOLD_TIMES_RIGHT((Idx>N? __extents.template __extent<Idx>():1),1);
+    }
+    template<size_t N>
+    constexpr index_type __stride() const noexcept {
+      return __get_stride<N>(__extents, std::make_index_sequence<extents_type::rank()>());
+    }
+
+private:
+   _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{};
+
+   // [mdspan.submdspan.mapping], submdspan mapping specialization
+   template<class... SliceSpecifiers>
+   MDSPAN_INLINE_FUNCTION
+   constexpr auto submdspan_mapping_impl(
+       SliceSpecifiers... slices) const;
+
+   template<class... SliceSpecifiers>
+     friend constexpr auto submdspan_mapping(
+       const mapping& src, SliceSpecifiers... slices) {
+         return src.submdspan_mapping_impl(slices...);
+     }
+};
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_stride.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_stride.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea2cd3802fd942d30dd8293217cc5c0dc498b8b1
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/layout_stride.hpp
@@ -0,0 +1,667 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+#include "extents.hpp"
+#include "trait_backports.hpp"
+#include "compressed_pair.hpp"
+#include "utility.hpp"
+
+#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+#  include "no_unique_address.hpp"
+#endif
+
+#include <array>
+#include <type_traits>
+#include <utility>
+
+#ifdef __cpp_lib_span
+#include <span>
+#endif
+#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts)
+#  include <concepts>
+#endif
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+struct layout_left {
+  template<class Extents>
+  class mapping;
+};
+struct layout_right {
+  template<class Extents>
+  class mapping;
+};
+
+namespace detail {
+  template<class Layout, class Mapping>
+  constexpr bool __is_mapping_of =
+    std::is_same<typename Layout::template mapping<typename Mapping::extents_type>, Mapping>::value;
+
+#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20
+#  if !defined(__cpp_lib_concepts)
+  namespace internal {
+  namespace detail {
+  template <typename _Tp, typename _Up>
+  concept __same_as = std::is_same_v<_Tp, _Up>;
+  } // namespace detail
+  template <class T, class U>
+  concept __same_as = detail::__same_as<T, U> && detail::__same_as<U, T>;
+  } // namespace internal
+#  endif
+
+  template<class M>
+  concept __layout_mapping_alike = requires {
+    requires __is_extents<typename M::extents_type>::value;
+#if defined(__cpp_lib_concepts)
+    { M::is_always_strided() } -> std::same_as<bool>;
+    { M::is_always_exhaustive() } -> std::same_as<bool>;
+    { M::is_always_unique() } -> std::same_as<bool>;
+#else
+    { M::is_always_strided() } -> internal::__same_as<bool>;
+    { M::is_always_exhaustive() } -> internal::__same_as<bool>;
+    { M::is_always_unique() } -> internal::__same_as<bool>;
+#endif
+    std::bool_constant<M::is_always_strided()>::value;
+    std::bool_constant<M::is_always_exhaustive()>::value;
+    std::bool_constant<M::is_always_unique()>::value;
+  };
+#endif
+
+} // namespace detail
+
+struct layout_stride {
+  template <class Extents>
+  class mapping
+#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+    : private detail::__no_unique_address_emulation<
+        detail::__compressed_pair<
+          Extents,
+          detail::possibly_empty_array<typename Extents::index_type, Extents::rank()>
+        >
+      >
+#endif
+  {
+  public:
+    using extents_type = Extents;
+    using index_type = typename extents_type::index_type;
+    using size_type = typename extents_type::size_type;
+    using rank_type = typename extents_type::rank_type;
+    using layout_type = layout_stride;
+
+    // This could be a `requires`, but I think it's better and clearer as a `static_assert`.
+    static_assert(detail::__is_extents_v<Extents>,
+                  MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents.");
+
+
+  private:
+
+    //----------------------------------------------------------------------------
+
+    using __strides_storage_t = detail::possibly_empty_array<index_type, extents_type::rank()>;
+    using __member_pair_t = detail::__compressed_pair<extents_type, __strides_storage_t>;
+
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+    _MDSPAN_NO_UNIQUE_ADDRESS __member_pair_t __members;
+#else
+    using __base_t = detail::__no_unique_address_emulation<__member_pair_t>;
+#endif
+
+    MDSPAN_FORCE_INLINE_FUNCTION constexpr __strides_storage_t const&
+    __strides_storage() const noexcept {
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      return __members.__second();
+#else
+      return this->__base_t::__ref().__second();
+#endif
+    }
+    MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 __strides_storage_t&
+    __strides_storage() noexcept {
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      return __members.__second();
+#else
+      return this->__base_t::__ref().__second();
+#endif
+    }
+
+    template<class SizeType, size_t ... Ep, size_t ... Idx>
+    _MDSPAN_HOST_DEVICE
+    constexpr index_type __get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<SizeType, Ep...>,std::integer_sequence<size_t, Idx...>) const {
+      return _MDSPAN_FOLD_TIMES_RIGHT( static_cast<index_type>(extents().extent(Idx)), 1 );
+    }
+
+    //----------------------------------------------------------------------------
+
+    template <class>
+    friend class mapping;
+
+    //----------------------------------------------------------------------------
+
+    // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level
+    template <class>
+    struct __deduction_workaround;
+
+    template <size_t... Idxs>
+    struct __deduction_workaround<std::index_sequence<Idxs...>>
+    {
+      template <class OtherExtents>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr bool _eq_impl(mapping const& self, mapping<OtherExtents> const& other) noexcept {
+        using common_t = std::common_type_t<index_type, typename OtherExtents::index_type>;
+        return    _MDSPAN_FOLD_AND((static_cast<common_t>(self.stride(Idxs)) == static_cast<common_t>(other.stride(Idxs))) /* && ... */)
+               && _MDSPAN_FOLD_AND((static_cast<common_t>(self.extents().extent(Idxs)) == static_cast<common_t>(other.extents().extent(Idxs))) /* || ... */);
+      }
+      template <class OtherExtents>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr bool _not_eq_impl(mapping const& self, mapping<OtherExtents> const& other) noexcept {
+        using common_t = std::common_type_t<index_type, typename OtherExtents::index_type>;
+        return    _MDSPAN_FOLD_OR((static_cast<common_t>(self.stride(Idxs)) != static_cast<common_t>(other.stride(Idxs))) /* || ... */)
+               || _MDSPAN_FOLD_OR((static_cast<common_t>(self.extents().extent(Idxs)) != static_cast<common_t>(other.extents().extent(Idxs))) /* || ... */);
+      }
+
+      template <class... Integral>
+      MDSPAN_FORCE_INLINE_FUNCTION
+      static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept {
+        return _MDSPAN_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0);
+      }
+
+      MDSPAN_INLINE_FUNCTION
+      static constexpr size_t _req_span_size_impl(mapping const& self) noexcept {
+        // assumes no negative strides; not sure if I'm allowed to assume that or not
+        return __impl::_call_op_impl(self, (self.extents().template __extent<Idxs>() - 1)...) + 1;
+      }
+
+      template<class OtherMapping>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr const __strides_storage_t fill_strides(const OtherMapping& map) {
+        return __strides_storage_t{static_cast<index_type>(map.stride(Idxs))...};
+      }
+
+      MDSPAN_INLINE_FUNCTION
+      static constexpr const __strides_storage_t& fill_strides(const __strides_storage_t& s) {
+        return s;
+      }
+
+      template<class IntegralType>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr const __strides_storage_t fill_strides(const std::array<IntegralType,extents_type::rank()>& s) {
+        return __strides_storage_t{static_cast<index_type>(s[Idxs])...};
+      }
+
+      MDSPAN_TEMPLATE_REQUIRES(
+        class IntegralType,
+        // The is_convertible condition is added to make sfinae valid
+        // the extents_type::rank() > 0 is added to avoid use of non-standard zero length c-array
+        (std::is_convertible<IntegralType, typename extents_type::index_type>::value && (extents_type::rank() > 0))
+      )
+      MDSPAN_INLINE_FUNCTION
+      // despite the requirement some compilers still complain about zero length array during parsing
+      // making it length 1 now, but since the thing can't be instantiated due to requirement the actual
+      // instantiation of strides_storage will not fail despite mismatching length
+      static constexpr const __strides_storage_t fill_strides(mdspan_non_standard_tag, const IntegralType (&s)[extents_type::rank()>0?extents_type::rank():1]) {
+        return __strides_storage_t{static_cast<index_type>(s[Idxs])...};
+      }
+
+#ifdef __cpp_lib_span
+      template<class IntegralType>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr const __strides_storage_t fill_strides(const std::span<IntegralType,extents_type::rank()>& s) {
+        return __strides_storage_t{static_cast<index_type>(s[Idxs])...};
+      }
+#endif
+
+      MDSPAN_INLINE_FUNCTION
+      static constexpr std::array<index_type, extents_type::rank()> return_strides(const __strides_storage_t& s) {
+        return std::array<index_type, extents_type::rank()>{s[Idxs]...};
+      }
+
+      template<size_t K>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr size_t __return_zero() { return 0; }
+
+      template<class Mapping>
+      MDSPAN_INLINE_FUNCTION
+      static constexpr typename Mapping::index_type
+        __OFFSET(const Mapping& m) { return m(__return_zero<Idxs>()...); }
+    };
+
+    // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348.
+    using __impl = __deduction_workaround<std::make_index_sequence<Extents::rank()>>;
+
+    static constexpr __strides_storage_t strides_storage(detail::with_rank<0>) {
+      return {};
+    }
+    template <std::size_t N>
+    static constexpr __strides_storage_t strides_storage(detail::with_rank<N>) {
+      __strides_storage_t s{};
+
+      extents_type e;
+      index_type stride = 1;
+      for(int r = static_cast<int>(extents_type::rank() - 1); r >= 0; r--) {
+        s[r] = stride;
+        stride *= e.extent(r);
+      }
+
+      return s;
+    }
+
+    //----------------------------------------------------------------------------
+
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+    MDSPAN_INLINE_FUNCTION constexpr explicit
+    mapping(__member_pair_t&& __m) : __members(::std::move(__m)) {}
+#else
+    MDSPAN_INLINE_FUNCTION constexpr explicit
+    mapping(__base_t&& __b) : __base_t(::std::move(__b)) {}
+#endif
+
+  public:
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      : __members{
+#else
+      : __base_t(__base_t{__member_pair_t(
+#endif
+          extents_type(),
+          __strides_storage_t(strides_storage(detail::with_rank<extents_type::rank()>{}))
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+        }
+#else
+        )})
+#endif
+    {}
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default;
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class IntegralTypes,
+      /* requires */ (
+        // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type
+        // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping'
+        _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t<IntegralTypes>&, typename Extents::index_type) &&
+        _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t<IntegralTypes>&)
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    constexpr
+    mapping(
+      extents_type const& e,
+      std::array<IntegralTypes, extents_type::rank()> const& s
+    ) noexcept
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      : __members{
+#else
+      : __base_t(__base_t{__member_pair_t(
+#endif
+          e, __strides_storage_t(__impl::fill_strides(s))
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+        }
+#else
+        )})
+#endif
+    {
+      /*
+       * TODO: check preconditions
+       * - s[i] > 0 is true for all i in the range [0, rank_ ).
+       * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]).
+       * - If rank_ is greater than 0, then there exists a permutation P of the integers in the
+       *   range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for
+       *   all i in the range [1, rank_ ), where pi is the ith element of P.
+       */
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class IntegralTypes,
+      /* requires */ (
+        // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type
+        // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping'
+        _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t<IntegralTypes>&, typename Extents::index_type) &&
+        _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t<IntegralTypes>&) &&
+        (Extents::rank() > 0)
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    constexpr
+    mapping(
+      mdspan_non_standard_tag,
+      extents_type const& e,
+      // despite the requirement some compilers still complain about zero length array during parsing
+      // making it length 1 now, but since the thing can't be instantiated due to requirement the actual
+      // instantiation of strides_storage will not fail despite mismatching length
+      IntegralTypes (&s)[extents_type::rank()>0?extents_type::rank():1]
+    ) noexcept
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      : __members{
+#else
+      : __base_t(__base_t{__member_pair_t(
+#endif
+          e, __strides_storage_t(__impl::fill_strides(mdspan_non_standard, s))
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+        }
+#else
+        )})
+#endif
+    {
+      /*
+       * TODO: check preconditions
+       * - s[i] > 0 is true for all i in the range [0, rank_ ).
+       * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]).
+       * - If rank_ is greater than 0, then there exists a permutation P of the integers in the
+       *   range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for
+       *   all i in the range [1, rank_ ), where pi is the ith element of P.
+       */
+    }
+
+#ifdef __cpp_lib_span
+    MDSPAN_TEMPLATE_REQUIRES(
+      class IntegralTypes,
+      /* requires */ (
+        // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type
+        // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping'
+        _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t<IntegralTypes>&, typename Extents::index_type) &&
+        _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t<IntegralTypes>&)
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    constexpr
+    mapping(
+      extents_type const& e,
+      std::span<IntegralTypes, extents_type::rank()> const& s
+    ) noexcept
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      : __members{
+#else
+      : __base_t(__base_t{__member_pair_t(
+#endif
+          e, __strides_storage_t(__impl::fill_strides(s))
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+        }
+#else
+        )})
+#endif
+    {
+      /*
+       * TODO: check preconditions
+       * - s[i] > 0 is true for all i in the range [0, rank_ ).
+       * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]).
+       * - If rank_ is greater than 0, then there exists a permutation P of the integers in the
+       *   range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for
+       *   all i in the range [1, rank_ ), where pi is the ith element of P.
+       */
+    }
+#endif // __cpp_lib_span
+
+#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20)
+    MDSPAN_TEMPLATE_REQUIRES(
+      class StridedLayoutMapping,
+      /* requires */ (
+        _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) &&
+        detail::__is_mapping_of<typename StridedLayoutMapping::layout_type, StridedLayoutMapping> &&
+        StridedLayoutMapping::is_always_unique() &&
+        StridedLayoutMapping::is_always_strided()
+      )
+    )
+#else
+    template<class StridedLayoutMapping>
+    requires(
+         detail::__layout_mapping_alike<StridedLayoutMapping> &&
+         _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) &&
+         StridedLayoutMapping::is_always_unique() &&
+         StridedLayoutMapping::is_always_strided()
+    )
+#endif
+    MDSPAN_CONDITIONAL_EXPLICIT(
+      !(std::is_convertible<typename StridedLayoutMapping::extents_type, extents_type>::value &&
+       (detail::__is_mapping_of<layout_left, StridedLayoutMapping> ||
+        detail::__is_mapping_of<layout_right, StridedLayoutMapping> ||
+        detail::__is_mapping_of<layout_stride, StridedLayoutMapping>))
+    ) // needs two () due to comma
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14
+    mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor)
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      : __members{
+#else
+      : __base_t(__base_t{__member_pair_t(
+#endif
+          other.extents(), __strides_storage_t(__impl::fill_strides(other))
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+        }
+#else
+        )})
+#endif
+    {
+      /*
+       * TODO: check preconditions
+       * - other.stride(i) > 0 is true for all i in the range [0, rank_ ).
+       * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]).
+       * - OFFSET(other) == 0
+       */
+    }
+
+    //--------------------------------------------------------------------------------
+
+    MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED
+    mapping& operator=(mapping const&) noexcept = default;
+
+    MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept {
+#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS)
+      return __members.__first();
+#else
+      return this->__base_t::__ref().__first();
+#endif
+    };
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr std::array< index_type, extents_type::rank() > strides() const noexcept {
+      return __impl::return_strides(__strides_storage());
+    }
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type required_span_size() const noexcept {
+      index_type span_size = 1;
+      for(unsigned r = 0; r < extents_type::rank(); r++) {
+        // Return early if any of the extents are zero
+        if(extents().extent(r)==0) return 0;
+        span_size += ( static_cast<index_type>(extents().extent(r) - 1 ) * __strides_storage()[r]);
+      }
+      return span_size;
+    }
+
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class... Indices,
+      /* requires */ (
+        sizeof...(Indices) == Extents::rank() &&
+        (detail::are_valid_indices<index_type, Indices...>())
+      )
+    )
+    MDSPAN_FORCE_INLINE_FUNCTION
+    constexpr index_type operator()(Indices... idxs) const noexcept {
+#if ! defined(NDEBUG)
+      detail::check_all_indices(this->extents(), idxs...);
+#endif // ! NDEBUG
+      return static_cast<index_type>(__impl::_call_op_impl(*this, static_cast<index_type>(idxs)...));
+    }
+
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept {
+      return false;
+    }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; }
+
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; }
+
+  private:
+    constexpr bool exhaustive_for_nonzero_span_size() const
+    {
+      return required_span_size() == __get_size(extents(), std::make_index_sequence<extents_type::rank()>());
+    }
+
+    constexpr bool is_exhaustive_impl(detail::with_rank<0>) const
+    {
+      return true;
+    }
+    constexpr bool is_exhaustive_impl(detail::with_rank<1>) const
+    {
+      if (required_span_size() != static_cast<index_type>(0)) {
+        return exhaustive_for_nonzero_span_size();
+      }
+      return stride(0) == 1;
+    }
+    template <std::size_t N>
+    constexpr bool is_exhaustive_impl(detail::with_rank<N>) const
+    {
+      if (required_span_size() != static_cast<index_type>(0)) {
+        return exhaustive_for_nonzero_span_size();
+      }
+
+      rank_type r_largest = 0;
+      for (rank_type r = 1; r < extents_type::rank(); r++) {
+        if (stride(r) > stride(r_largest)) {
+          r_largest = r;
+        }
+      }
+      for (rank_type r = 0; r < extents_type::rank(); r++) {
+        if (extents().extent(r) == 0 && r != r_largest) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+  public:
+    MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 bool is_exhaustive() const noexcept {
+      return is_exhaustive_impl(detail::with_rank<extents_type::rank()>{});
+    }
+    MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; }
+
+
+    MDSPAN_INLINE_FUNCTION
+    constexpr index_type stride(rank_type r) const noexcept {
+      return __strides_storage()[r];
+    }
+
+#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20)
+    MDSPAN_TEMPLATE_REQUIRES(
+      class StridedLayoutMapping,
+      /* requires */ (
+        detail::__is_mapping_of<typename StridedLayoutMapping::layout_type, StridedLayoutMapping> &&
+        (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) &&
+        StridedLayoutMapping::is_always_strided()
+      )
+    )
+#else
+    template<class StridedLayoutMapping>
+    requires(
+         detail::__layout_mapping_alike<StridedLayoutMapping> &&
+         (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) &&
+         StridedLayoutMapping::is_always_strided()
+    )
+#endif
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept {
+      return (x.extents() == y.extents()) &&
+             (__impl::__OFFSET(y) == static_cast<typename StridedLayoutMapping::index_type>(0)) &&
+             detail::rankwise_equal(detail::with_rank<extents_type::rank()>{}, x, y, detail::stride);
+    }
+
+    // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        (extents_type::rank() == OtherExtents::rank())
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator==(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return __impl::_eq_impl(lhs, rhs);
+    }
+
+#if !MDSPAN_HAS_CXX_20
+    MDSPAN_TEMPLATE_REQUIRES(
+      class StridedLayoutMapping,
+      /* requires */ (
+        detail::__is_mapping_of<typename StridedLayoutMapping::layout_type, StridedLayoutMapping> &&
+        (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) &&
+        StridedLayoutMapping::is_always_strided()
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept {
+      return not (x == y);
+    }
+
+    MDSPAN_TEMPLATE_REQUIRES(
+      class OtherExtents,
+      /* requires */ (
+        (extents_type::rank() == OtherExtents::rank())
+      )
+    )
+    MDSPAN_INLINE_FUNCTION
+    friend constexpr bool operator!=(mapping const& lhs, mapping<OtherExtents> const& rhs) noexcept {
+      return __impl::_not_eq_impl(lhs, rhs);
+    }
+#endif
+
+   // [mdspan.submdspan.mapping], submdspan mapping specialization
+   template<class... SliceSpecifiers>
+   MDSPAN_INLINE_FUNCTION
+   constexpr auto submdspan_mapping_impl(
+       SliceSpecifiers... slices) const;
+
+   template<class... SliceSpecifiers>
+     friend constexpr auto submdspan_mapping(
+       const mapping& src, SliceSpecifiers... slices) {
+      return src.submdspan_mapping_impl(slices...);
+    }
+  };
+};
+
+namespace detail {
+
+template <class Layout, class Extents, class Mapping>
+constexpr void validate_strides(with_rank<0>, Layout, const Extents&, const Mapping&)
+{}
+
+template <std::size_t N, class Layout, class Extents, class Mapping>
+constexpr void validate_strides(with_rank<N>, Layout, const Extents& ext, const Mapping& other)
+{
+  static_assert(std::is_same<typename Mapping::layout_type, layout_stride>::value and
+                (std::is_same<Layout, layout_left>::value or
+                 std::is_same<Layout, layout_right>::value)
+                , "This function is only intended to validate construction of "
+                  "a layout_left or layout_right mapping from a layout_stride mapping.");
+
+  constexpr auto is_left = std::is_same<Layout, layout_left>::value;
+
+  typename Extents::index_type expected_stride = 1;
+
+  for (std::size_t r = 0; r < N; r++) {
+    const std::size_t s = is_left ? r : N - 1 - r;
+
+    MDSPAN_IMPL_PRECONDITION(common_integral_compare(expected_stride, other.stride(s))
+                             and "invalid strides for layout_{left,right}");
+
+    expected_stride *= ext.extent(s);
+  }
+}
+
+} // namespace detail
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/macros.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/macros.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..30209a6648b6ef8cc4d32d7bb49411275bc24a8f
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/macros.hpp
@@ -0,0 +1,699 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include "config.hpp"
+
+#include <cstdio>
+#include <cstdlib>
+#include <type_traits> // std::is_void
+#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_SYCL)
+#include "assert.h"
+#endif
+
+#ifndef _MDSPAN_HOST_DEVICE
+#  if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP)
+#    define _MDSPAN_HOST_DEVICE __host__ __device__
+#  else
+#    define _MDSPAN_HOST_DEVICE
+#  endif
+#endif
+
+#ifndef MDSPAN_FORCE_INLINE_FUNCTION
+#  ifdef _MDSPAN_COMPILER_MSVC // Microsoft compilers
+#    define MDSPAN_FORCE_INLINE_FUNCTION __forceinline _MDSPAN_HOST_DEVICE
+#  else
+#    define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) _MDSPAN_HOST_DEVICE
+#  endif
+#endif
+
+#ifndef MDSPAN_INLINE_FUNCTION
+#  define MDSPAN_INLINE_FUNCTION inline _MDSPAN_HOST_DEVICE
+#endif
+
+#ifndef MDSPAN_FUNCTION
+#  define MDSPAN_FUNCTION _MDSPAN_HOST_DEVICE
+#endif
+
+#ifdef _MDSPAN_HAS_HIP
+#  define MDSPAN_DEDUCTION_GUIDE _MDSPAN_HOST_DEVICE
+#else
+#  define MDSPAN_DEDUCTION_GUIDE
+#endif
+
+// In CUDA defaulted functions do not need host device markup
+#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED
+#  define MDSPAN_INLINE_FUNCTION_DEFAULTED
+#endif
+
+//==============================================================================
+// <editor-fold desc="Preprocessor helpers"> {{{1
+
+#define MDSPAN_PP_COUNT(...) \
+  _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE( \
+    _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \
+  )
+
+#define _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
+#define _MDSPAN_PP_INTERNAL_EXPAND(x) x
+#define _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE(...) \
+  _MDSPAN_PP_INTERNAL_EXPAND( \
+    _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \
+      __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \
+      60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49,  \
+      48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37,  \
+      36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25,  \
+      24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13,  \
+      12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \
+    ) \
+  )
+# define _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \
+         _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, \
+    _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
+    _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
+    _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
+    _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \
+    _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \
+    _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, \
+    _70, count, ...) count \
+    /**/
+
+#define MDSPAN_PP_STRINGIFY_IMPL(x) #x
+#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x)
+
+#define MDSPAN_PP_CAT_IMPL(x, y) x ## y
+#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y)
+
+#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__)
+
+#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__
+#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__
+
+#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE)
+#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE)
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP)
+MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line)
+{
+  printf("%s:%u: precondition failure: `%s`\n", file, line, cond);
+  assert(0);
+}
+#elif defined(_MDSPAN_HAS_SYCL)
+MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line)
+{
+  sycl::ext::oneapi::experimental::printf("%s:%u: precondition failure: `%s`\n", file, line, cond);
+  assert(0);
+}
+#else
+MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line)
+{
+  std::fprintf(stderr, "%s:%u: precondition failure: `%s`\n", file, line, cond);
+  std::abort();
+}
+#endif
+
+} // namespace detail
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#ifndef MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER
+#define MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line) \
+  MDSPAN_IMPL_STANDARD_NAMESPACE::detail::default_precondition_violation_handler(cond, file, line)
+#endif
+
+#ifndef MDSPAN_IMPL_CHECK_PRECONDITION
+  #ifndef NDEBUG
+    #define MDSPAN_IMPL_CHECK_PRECONDITION 0
+  #else
+    #define MDSPAN_IMPL_CHECK_PRECONDITION 1
+  #endif
+#endif
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+template <bool check = MDSPAN_IMPL_CHECK_PRECONDITION>
+MDSPAN_FUNCTION constexpr void precondition(const char* cond, const char* file, unsigned line)
+{
+  if (not check) { return; }
+  // in case the macro doesn't use the arguments for custom macros
+  (void) cond;
+  (void) file;
+  (void) line;
+  MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line);
+}
+
+} // namespace detail
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#define MDSPAN_IMPL_PRECONDITION(...) \
+  do { \
+    if (not (__VA_ARGS__)) { \
+      MDSPAN_IMPL_STANDARD_NAMESPACE::detail::precondition(#__VA_ARGS__, __FILE__, __LINE__); \
+    } \
+  } while (0)
+
+// </editor-fold> end Preprocessor helpers }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="Concept emulation"> {{{1
+
+// These compatibility macros don't help with partial ordering, but they should do the trick
+// for what we need to do with concepts in mdspan
+#ifdef _MDSPAN_USE_CONCEPTS
+#  define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ
+#  define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \
+     MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \
+     /**/
+#else
+#  define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0>
+#  define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \
+     MDSPAN_TEMPLATE_REQUIRES( \
+       class __function_requires_ignored=void, \
+       (std::is_void<__function_requires_ignored>::value && REQ) \
+     ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \
+     /**/
+#endif
+
+#if defined(_MDSPAN_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL)
+#  define MDSPAN_TEMPLATE_REQUIRES(...) \
+      MDSPAN_PP_CAT( \
+        MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\
+        (__VA_ARGS__), \
+      ) \
+    /**/
+#else
+#  define MDSPAN_TEMPLATE_REQUIRES(...) \
+    MDSPAN_PP_EVAL( \
+        MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \
+        __VA_ARGS__ \
+    ) \
+    /**/
+#endif
+
+#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \
+  template<TP1 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_3(TP1, TP2, REQ) \
+  template<TP1, TP2 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_4(TP1, TP2, TP3, REQ) \
+  template<TP1, TP2, TP3 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_5(TP1, TP2, TP3, TP4, REQ) \
+  template<TP1, TP2, TP3, TP4 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_6(TP1, TP2, TP3, TP4, TP5, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_7(TP1, TP2, TP3, TP4, TP5, TP6, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_8(TP1, TP2, TP3, TP4, TP5, TP6, TP7, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_9(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_10(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_11(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_12(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_13(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_14(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_15(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_16(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_17(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_18(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_19(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17, TP18, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17, TP18 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+#define MDSPAN_TEMPLATE_REQUIRES_20(TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17, TP18, TP19, REQ) \
+  template<TP1, TP2, TP3, TP4, TP5, TP6, TP7, TP8, TP9, TP10, TP11, TP12, TP13, TP14, TP15, TP16, TP17, TP18, TP19 \
+    MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) \
+    /**/
+
+#define MDSPAN_INSTANTIATE_ONLY_IF_USED \
+  MDSPAN_TEMPLATE_REQUIRES( \
+    class __instantiate_only_if_used_tparam=void, \
+    ( _MDSPAN_TRAIT(std::is_void, __instantiate_only_if_used_tparam) ) \
+  ) \
+  /**/
+
+// </editor-fold> end Concept emulation }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="inline variables"> {{{1
+
+#ifdef _MDSPAN_USE_INLINE_VARIABLES
+#  define _MDSPAN_INLINE_VARIABLE inline
+#else
+#  define _MDSPAN_INLINE_VARIABLE
+#endif
+
+// </editor-fold> end inline variables }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="Return type deduction"> {{{1
+
+#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION
+#  define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \
+    auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); }
+#  define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \
+    decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); }
+#else
+#  define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \
+    auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \
+      -> std::remove_cv_t<std::remove_reference_t<decltype(BODY)>> \
+    { return MDSPAN_PP_REMOVE_PARENS(BODY); }
+#  define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \
+    auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \
+      -> decltype(BODY) \
+    { return MDSPAN_PP_REMOVE_PARENS(BODY); }
+
+#endif
+
+// </editor-fold> end Return type deduction }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="fold expressions"> {{{1
+
+struct __mdspan_enable_fold_comma { };
+
+#ifdef _MDSPAN_USE_FOLD_EXPRESSIONS
+#  define _MDSPAN_FOLD_AND(...) ((__VA_ARGS__) && ...)
+#  define _MDSPAN_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...)
+#  define _MDSPAN_FOLD_OR(...) ((__VA_ARGS__) || ...)
+#  define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__))
+#  define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__))
+#  define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__))
+#  define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__))
+#  define _MDSPAN_FOLD_COMMA(...) ((__VA_ARGS__), ...)
+#else
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+namespace __fold_compatibility_impl {
+
+// We could probably be more clever here, but at the (small) risk of losing some compiler understanding.  For the
+// few operations we need, it's not worth generalizing over the operation
+
+#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION
+
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr decltype(auto) __fold_right_and_impl() {
+  return true;
+}
+
+template <class Arg, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr decltype(auto) __fold_right_and_impl(Arg&& arg, Args&&... args) {
+  return ((Arg&&)arg) && __fold_compatibility_impl::__fold_right_and_impl((Args&&)args...);
+}
+
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr decltype(auto) __fold_right_or_impl() {
+  return false;
+}
+
+template <class Arg, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_or_impl(Arg&& arg, Args&&... args) {
+  return ((Arg&&)arg) || __fold_compatibility_impl::__fold_right_or_impl((Args&&)args...);
+}
+
+template <class Arg1>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_left_assign_impl(Arg1&& arg1) {
+  return (Arg1&&)arg1;
+}
+
+template <class Arg1, class Arg2, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) {
+  return __fold_compatibility_impl::__fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...);
+}
+
+template <class Arg1>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_assign_impl(Arg1&& arg1) {
+  return (Arg1&&)arg1;
+}
+
+template <class Arg1, class Arg2, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2,  Args&&... args) {
+  return ((Arg1&&)arg1) = __fold_compatibility_impl::__fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...);
+}
+
+template <class Arg1>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_plus_impl(Arg1&& arg1) {
+  return (Arg1&&)arg1;
+}
+
+template <class Arg1, class Arg2, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) {
+  return ((Arg1&&)arg1) + __fold_compatibility_impl::__fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...);
+}
+
+template <class Arg1>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_times_impl(Arg1&& arg1) {
+  return (Arg1&&)arg1;
+}
+
+template <class Arg1, class Arg2, class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr auto __fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) {
+  return ((Arg1&&)arg1) * __fold_compatibility_impl::__fold_right_times_impl((Arg2&&)arg2, (Args&&)args...);
+}
+
+#else
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="right and"> {{{2
+
+template <class... Args>
+struct __fold_right_and_impl_;
+template <>
+struct __fold_right_and_impl_<> {
+  using __rv = bool;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl() noexcept {
+    return true;
+  }
+};
+template <class Arg, class... Args>
+struct __fold_right_and_impl_<Arg, Args...> {
+  using __next_t = __fold_right_and_impl_<Args...>;
+  using __rv = decltype(std::declval<Arg>() && std::declval<typename __next_t::__rv>());
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg, Args&&... args) noexcept {
+    return ((Arg&&)arg) && __next_t::__impl((Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_right_and_impl_<Args...>::__rv
+__fold_right_and_impl(Args&&... args) {
+  return __fold_right_and_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end right and }}}2
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="right or"> {{{2
+
+template <class... Args>
+struct __fold_right_or_impl_;
+template <>
+struct __fold_right_or_impl_<> {
+  using __rv = bool;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl() noexcept {
+    return false;
+  }
+};
+template <class Arg, class... Args>
+struct __fold_right_or_impl_<Arg, Args...> {
+  using __next_t = __fold_right_or_impl_<Args...>;
+  using __rv = decltype(std::declval<Arg>() || std::declval<typename __next_t::__rv>());
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg, Args&&... args) noexcept {
+    return ((Arg&&)arg) || __next_t::__impl((Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_right_or_impl_<Args...>::__rv
+__fold_right_or_impl(Args&&... args) {
+  return __fold_right_or_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end right or }}}2
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="right plus"> {{{2
+
+template <class... Args>
+struct __fold_right_plus_impl_;
+template <class Arg>
+struct __fold_right_plus_impl_<Arg> {
+  using __rv = Arg&&;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg) noexcept {
+    return (Arg&&)arg;
+  }
+};
+template <class Arg1, class Arg2, class... Args>
+struct __fold_right_plus_impl_<Arg1, Arg2, Args...> {
+  using __next_t = __fold_right_plus_impl_<Arg2, Args...>;
+  using __rv = decltype(std::declval<Arg1>() + std::declval<typename __next_t::__rv>());
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept {
+    return ((Arg1&&)arg) + __next_t::__impl((Arg2&&)arg2, (Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_right_plus_impl_<Args...>::__rv
+__fold_right_plus_impl(Args&&... args) {
+  return __fold_right_plus_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end right plus }}}2
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="right times"> {{{2
+
+template <class... Args>
+struct __fold_right_times_impl_;
+template <class Arg>
+struct __fold_right_times_impl_<Arg> {
+  using __rv = Arg&&;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg) noexcept {
+    return (Arg&&)arg;
+  }
+};
+template <class Arg1, class Arg2, class... Args>
+struct __fold_right_times_impl_<Arg1, Arg2, Args...> {
+  using __next_t = __fold_right_times_impl_<Arg2, Args...>;
+  using __rv = decltype(std::declval<Arg1>() * std::declval<typename __next_t::__rv>());
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept {
+    return ((Arg1&&)arg) * __next_t::__impl((Arg2&&)arg2, (Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_right_times_impl_<Args...>::__rv
+__fold_right_times_impl(Args&&... args) {
+  return __fold_right_times_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end right times }}}2
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="right assign"> {{{2
+
+template <class... Args>
+struct __fold_right_assign_impl_;
+template <class Arg>
+struct __fold_right_assign_impl_<Arg> {
+  using __rv = Arg&&;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg) noexcept {
+    return (Arg&&)arg;
+  }
+};
+template <class Arg1, class Arg2, class... Args>
+struct __fold_right_assign_impl_<Arg1, Arg2, Args...> {
+  using __next_t = __fold_right_assign_impl_<Arg2, Args...>;
+  using __rv = decltype(std::declval<Arg1>() = std::declval<typename __next_t::__rv>());
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept {
+    return ((Arg1&&)arg) = __next_t::__impl((Arg2&&)arg2, (Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_right_assign_impl_<Args...>::__rv
+__fold_right_assign_impl(Args&&... args) {
+  return __fold_right_assign_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end right assign }}}2
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// <editor-fold desc="left assign"> {{{2
+
+template <class... Args>
+struct __fold_left_assign_impl_;
+template <class Arg>
+struct __fold_left_assign_impl_<Arg> {
+  using __rv = Arg&&;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg&& arg) noexcept {
+    return (Arg&&)arg;
+  }
+};
+template <class Arg1, class Arg2, class... Args>
+struct __fold_left_assign_impl_<Arg1, Arg2, Args...> {
+  using __assign_result_t = decltype(std::declval<Arg1>() = std::declval<Arg2>());
+  using __next_t = __fold_left_assign_impl_<__assign_result_t, Args...>;
+  using __rv = typename __next_t::__rv;
+  MDSPAN_FORCE_INLINE_FUNCTION
+  static constexpr __rv
+  __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept {
+    return __next_t::__impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...);
+  }
+};
+
+template <class... Args>
+MDSPAN_FORCE_INLINE_FUNCTION
+constexpr typename __fold_left_assign_impl_<Args...>::__rv
+__fold_left_assign_impl(Args&&... args) {
+  return __fold_left_assign_impl_<Args...>::__impl((Args&&)args...);
+}
+
+// </editor-fold> end left assign }}}2
+//------------------------------------------------------------------------------
+
+#endif
+
+
+template <class... Args>
+constexpr __mdspan_enable_fold_comma __fold_comma_impl(Args&&... args) noexcept { return { }; }
+
+template <bool... Bs>
+struct __bools;
+
+} // __fold_compatibility_impl
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#  define _MDSPAN_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_and_impl((__VA_ARGS__)...)
+#  define _MDSPAN_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_or_impl((__VA_ARGS__)...)
+#  define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_left_assign_impl(INIT, (__VA_ARGS__)...)
+#  define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_assign_impl((PACK)..., __VA_ARGS__)
+#  define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_times_impl((PACK)..., __VA_ARGS__)
+#  define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_plus_impl((PACK)..., __VA_ARGS__)
+#  define _MDSPAN_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_comma_impl((__VA_ARGS__)...)
+
+#  define _MDSPAN_FOLD_AND_TEMPLATE(...) \
+  _MDSPAN_TRAIT(std::is_same, __fold_compatibility_impl::__bools<(__VA_ARGS__)..., true>, __fold_compatibility_impl::__bools<true, (__VA_ARGS__)...>)
+
+#endif
+
+// </editor-fold> end fold expressions }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="Variable template compatibility"> {{{1
+
+#if _MDSPAN_USE_VARIABLE_TEMPLATES
+#  define _MDSPAN_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__>
+#else
+#  define _MDSPAN_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value
+#endif
+
+// </editor-fold> end Variable template compatibility }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="Pre-C++14 constexpr"> {{{1
+
+#if _MDSPAN_USE_CONSTEXPR_14
+#  define _MDSPAN_CONSTEXPR_14 constexpr
+// Workaround for a bug (I think?) in EDG frontends
+#  ifdef __EDG__
+#    define _MDSPAN_CONSTEXPR_14_DEFAULTED
+#  else
+#    define _MDSPAN_CONSTEXPR_14_DEFAULTED constexpr
+#  endif
+#else
+#  define _MDSPAN_CONSTEXPR_14
+#  define _MDSPAN_CONSTEXPR_14_DEFAULTED
+#endif
+
+// </editor-fold> end Pre-C++14 constexpr }}}1
+//==============================================================================
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/mdspan.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/mdspan.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..af4848248d64ced5f5ef0d383561db73a3ee7579
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/mdspan.hpp
@@ -0,0 +1,432 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include "default_accessor.hpp"
+#include "layout_right.hpp"
+#include "extents.hpp"
+#include "trait_backports.hpp"
+#include "compressed_pair.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+template <
+  class ElementType,
+  class Extents,
+  class LayoutPolicy = layout_right,
+  class AccessorPolicy = default_accessor<ElementType>
+>
+class mdspan
+{
+private:
+  static_assert(detail::__is_extents_v<Extents>,
+                MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents.");
+  static_assert(std::is_same<ElementType, typename AccessorPolicy::element_type>::value,
+                MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's ElementType template parameter must be the same as its AccessorPolicy::element_type.");
+
+  // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level
+  template <class>
+  struct __deduction_workaround;
+
+  template <size_t... Idxs>
+  struct __deduction_workaround<std::index_sequence<Idxs...>>
+  {
+    MDSPAN_FORCE_INLINE_FUNCTION static constexpr
+    size_t __size(mdspan const& __self) noexcept {
+      return _MDSPAN_FOLD_TIMES_RIGHT((__self.__mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1));
+    }
+    MDSPAN_FORCE_INLINE_FUNCTION static constexpr
+    bool __empty(mdspan const& __self) noexcept {
+      return (__self.rank()>0) && _MDSPAN_FOLD_OR((__self.__mapping_ref().extents().extent(Idxs)==index_type(0)));
+    }
+    template <class ReferenceType, class SizeType, size_t N>
+    MDSPAN_FORCE_INLINE_FUNCTION static constexpr
+    ReferenceType __callop(mdspan const& __self, const std::array<SizeType, N>& indices) noexcept {
+      return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...));
+    }
+#ifdef __cpp_lib_span
+    template <class ReferenceType, class SizeType, size_t N>
+    MDSPAN_FORCE_INLINE_FUNCTION static constexpr
+    ReferenceType __callop(mdspan const& __self, const std::span<SizeType, N>& indices) noexcept {
+      return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...));
+    }
+#endif
+  };
+
+public:
+
+  //--------------------------------------------------------------------------------
+  // Domain and codomain types
+
+  using extents_type = Extents;
+  using layout_type = LayoutPolicy;
+  using accessor_type = AccessorPolicy;
+  using mapping_type = typename layout_type::template mapping<extents_type>;
+  using element_type = ElementType;
+  using value_type = std::remove_cv_t<element_type>;
+  using index_type = typename extents_type::index_type;
+  using size_type = typename extents_type::size_type;
+  using rank_type = typename extents_type::rank_type;
+  using data_handle_type = typename accessor_type::data_handle_type;
+  using reference = typename accessor_type::reference;
+
+  MDSPAN_INLINE_FUNCTION static constexpr size_t rank() noexcept { return extents_type::rank(); }
+  MDSPAN_INLINE_FUNCTION static constexpr size_t rank_dynamic() noexcept { return extents_type::rank_dynamic(); }
+  MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); }
+  MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return __mapping_ref().extents().extent(r); };
+
+private:
+
+  // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348.
+  using __impl = __deduction_workaround<std::make_index_sequence<extents_type::rank()>>;
+
+  using __map_acc_pair_t = detail::__compressed_pair<mapping_type, accessor_type>;
+
+public:
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.cons], mdspan constructors, assignment, and destructor
+
+#if !MDSPAN_HAS_CXX_20
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default;
+#else
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan()
+    requires(
+       // nvhpc has a bug where using just rank_dynamic() here doesn't work ...
+       (extents_type::rank_dynamic() > 0) &&
+       _MDSPAN_TRAIT(std::is_default_constructible, data_handle_type) &&
+       _MDSPAN_TRAIT(std::is_default_constructible, mapping_type) &&
+       _MDSPAN_TRAIT(std::is_default_constructible, accessor_type)
+     ) = default;
+#endif
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default;
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) &&
+      (detail::are_valid_indices<index_type, SizeTypes...>()) &&
+      _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) &&
+      _MDSPAN_TRAIT(std::is_default_constructible, accessor_type)
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents)
+    // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here?
+    : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(static_cast<index_type>(std::move(dynamic_extents))...)), accessor_type()))
+  { }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) &&
+      ((N == rank()) || (N == rank_dynamic())) &&
+      _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) &&
+      _MDSPAN_TRAIT(std::is_default_constructible, accessor_type)
+    )
+  )
+  MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic())
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdspan(data_handle_type p, const std::array<SizeType, N>& dynamic_extents)
+    : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type()))
+  { }
+
+#ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) &&
+      ((N == rank()) || (N == rank_dynamic())) &&
+      _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) &&
+      _MDSPAN_TRAIT(std::is_default_constructible, accessor_type)
+    )
+  )
+  MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic())
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdspan(data_handle_type p, std::span<SizeType, N> dynamic_extents)
+    : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type()))
+  { }
+#endif
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdspan, (data_handle_type p, const extents_type& exts), ,
+    /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type) &&
+                    _MDSPAN_TRAIT(std::is_constructible, mapping_type, const extents_type&))
+  ) : __members(std::move(p), __map_acc_pair_t(mapping_type(exts), accessor_type()))
+  { }
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdspan, (data_handle_type p, const mapping_type& m), ,
+    /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type))
+  ) : __members(std::move(p), __map_acc_pair_t(m, accessor_type()))
+  { }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a)
+    : __members(std::move(p), __map_acc_pair_t(m, a))
+  { }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_constructible, mapping_type, const typename OtherLayoutPolicy::template mapping<OtherExtents>&) &&
+      _MDSPAN_TRAIT(std::is_constructible, accessor_type, const OtherAccessor&)
+    )
+  )
+  MDSPAN_CONDITIONAL_EXPLICIT(
+    !_MDSPAN_TRAIT(std::is_convertible, const typename OtherLayoutPolicy::template mapping<OtherExtents>&, mapping_type) ||
+    !_MDSPAN_TRAIT(std::is_convertible, const OtherAccessor&, accessor_type)
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdspan(const mdspan<OtherElementType, OtherExtents, OtherLayoutPolicy, OtherAccessor>& other)
+    : __members(other.__ptr_ref(), __map_acc_pair_t(other.__mapping_ref(), other.__accessor_ref()))
+  {
+      static_assert(_MDSPAN_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction");
+      static_assert(_MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction");
+      /*
+       * TODO: Check precondition
+       * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true.
+       */
+  }
+
+  /* Might need this on NVIDIA?
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~mdspan() = default;
+  */
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default;
+
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element
+
+  #if MDSPAN_USE_BRACKET_OPERATOR
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) &&
+      _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) &&
+      (rank() == sizeof...(SizeTypes))
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](SizeTypes... indices) const
+  {
+    return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast<index_type>(std::move(indices))...));
+  }
+  #endif
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](const std::array< SizeType, rank()>& indices) const
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+
+  #ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](std::span<SizeType, rank()> indices) const
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+  #endif // __cpp_lib_span
+
+  #if !MDSPAN_USE_BRACKET_OPERATOR
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Index,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, Index, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Index) &&
+      extents_type::rank() == 1
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](Index idx) const
+  {
+    return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast<index_type>(std::move(idx))));
+  }
+  #endif
+
+  #if MDSPAN_USE_PAREN_OPERATOR
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      extents_type::rank() == sizeof...(SizeTypes) &&
+      (detail::are_valid_indices<index_type, SizeTypes...>())
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator()(SizeTypes... indices) const
+  {
+    return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast<index_type>(std::move(indices))...));
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator()(const std::array<SizeType, rank()>& indices) const
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+
+  #ifdef __cpp_lib_span
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) &&
+      _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator()(std::span<SizeType, rank()> indices) const
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+  #endif // __cpp_lib_span
+  #endif // MDSPAN_USE_PAREN_OPERATOR
+
+  MDSPAN_INLINE_FUNCTION constexpr size_type size() const noexcept {
+    return __impl::__size(*this);
+  };
+
+  MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept {
+    return __impl::__empty(*this);
+  };
+
+  MDSPAN_INLINE_FUNCTION
+  friend constexpr void swap(mdspan& x, mdspan& y) noexcept {
+    // can't call the std::swap inside on HIP
+    #if !defined(_MDSPAN_HAS_HIP) && !defined(_MDSPAN_HAS_CUDA)
+    using std::swap;
+    swap(x.__ptr_ref(), y.__ptr_ref());
+    swap(x.__mapping_ref(), y.__mapping_ref());
+    swap(x.__accessor_ref(), y.__accessor_ref());
+    #else
+    mdspan tmp = y;
+    y = x;
+    x = tmp;
+    #endif
+  }
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space
+
+
+  MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return __mapping_ref().extents(); };
+  MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return __ptr_ref(); };
+  MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return __mapping_ref(); };
+  MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return __accessor_ref(); };
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.obs], mdspan observers of the mapping
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); };
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); };
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); };
+
+  MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const { return __mapping_ref().is_unique(); };
+  MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const { return __mapping_ref().is_exhaustive(); };
+  MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const { return __mapping_ref().is_strided(); };
+  MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return __mapping_ref().stride(r); };
+
+private:
+
+  detail::__compressed_pair<data_handle_type, __map_acc_pair_t> __members{};
+
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 data_handle_type& __ptr_ref() noexcept { return __members.__first(); }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& __ptr_ref() const noexcept { return __members.__first(); }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 mapping_type& __mapping_ref() noexcept { return __members.__second().__first(); }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& __mapping_ref() const noexcept { return __members.__second().__first(); }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 accessor_type& __accessor_ref() noexcept { return __members.__second().__second(); }
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& __accessor_ref() const noexcept { return __members.__second().__second(); }
+
+  template <class, class, class, class>
+  friend class mdspan;
+
+};
+
+#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION)
+MDSPAN_TEMPLATE_REQUIRES(
+  class ElementType, class... SizeTypes,
+  /* requires */ _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, size_t) /* && ... */) &&
+  (sizeof...(SizeTypes) > 0)
+)
+MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...)
+  -> mdspan<ElementType, ::MDSPAN_IMPL_STANDARD_NAMESPACE::dextents<size_t, sizeof...(SizeTypes)>>;
+
+MDSPAN_TEMPLATE_REQUIRES(
+  class Pointer,
+  (_MDSPAN_TRAIT(std::is_pointer, std::remove_reference_t<Pointer>))
+)
+MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan<std::remove_pointer_t<std::remove_reference_t<Pointer>>, extents<size_t>>;
+
+MDSPAN_TEMPLATE_REQUIRES(
+  class CArray,
+  (_MDSPAN_TRAIT(std::is_array, CArray) && (std::rank_v<CArray> == 1))
+)
+MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan<std::remove_all_extents_t<CArray>, extents<size_t, ::std::extent_v<CArray,0>>>;
+
+template <class ElementType, class SizeType, size_t N>
+MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array<SizeType, N>&)
+  -> mdspan<ElementType, ::MDSPAN_IMPL_STANDARD_NAMESPACE::dextents<size_t, N>>;
+
+#ifdef __cpp_lib_span
+template <class ElementType, class SizeType, size_t N>
+MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span<SizeType, N>)
+  -> mdspan<ElementType, ::MDSPAN_IMPL_STANDARD_NAMESPACE::dextents<size_t, N>>;
+#endif
+
+// This one is necessary because all the constructors take `data_handle_type`s, not
+// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which
+// seems to throw off automatic deduction guides.
+template <class ElementType, class SizeType, size_t... ExtentsPack>
+MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents<SizeType, ExtentsPack...>&)
+  -> mdspan<ElementType, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents<SizeType, ExtentsPack...>>;
+
+template <class ElementType, class MappingType>
+MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&)
+  -> mdspan<ElementType, typename MappingType::extents_type, typename MappingType::layout_type>;
+
+template <class MappingType, class AccessorType>
+MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&)
+  -> mdspan<typename AccessorType::element_type, typename MappingType::extents_type, typename MappingType::layout_type, AccessorType>;
+#endif
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/no_unique_address.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/no_unique_address.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..36e64ee24dbb7166ac5da069bbbfbc4347026993
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/no_unique_address.hpp
@@ -0,0 +1,97 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include "macros.hpp"
+#include "trait_backports.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+//==============================================================================
+
+template <class _T, size_t _Disambiguator = 0, class _Enable = void>
+struct __no_unique_address_emulation {
+  using __stored_type = _T;
+  _T __v;
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept {
+    return __v;
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept {
+    return __v;
+  }
+};
+
+// Empty case
+// This doesn't work if _T is final, of course, but we're not using anything
+// like that currently. That kind of thing could be added pretty easily though
+template <class _T, size_t _Disambiguator>
+struct __no_unique_address_emulation<
+    _T, _Disambiguator,
+    std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) &&
+                // If the type isn't trivially destructible, its destructor
+                // won't be called at the right time, so don't use this
+                // specialization
+                _MDSPAN_TRAIT(std::is_trivially_destructible, _T)>> :
+#ifdef _MDSPAN_COMPILER_MSVC
+    // MSVC doesn't allow you to access public static member functions of a type
+    // when you *happen* to privately inherit from that type.
+    protected
+#else
+    // But we still want this to be private if possible so that we don't accidentally
+    // access members of _T directly rather than calling __ref() first, which wouldn't
+    // work if _T happens to be stateful and thus we're using the unspecialized definition
+    // of __no_unique_address_emulation above.
+    private
+#endif
+    _T {
+  using __stored_type = _T;
+  MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept {
+    return *static_cast<_T const *>(this);
+  }
+  MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept {
+    return *static_cast<_T *>(this);
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __no_unique_address_emulation() noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __no_unique_address_emulation(
+      __no_unique_address_emulation const &) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr __no_unique_address_emulation(
+      __no_unique_address_emulation &&) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation &
+  operator=(__no_unique_address_emulation const &) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation &
+  operator=(__no_unique_address_emulation &&) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~__no_unique_address_emulation() noexcept = default;
+
+  // Explicitly make this not a reference so that the copy or move
+  // constructor still gets called.
+  MDSPAN_INLINE_FUNCTION
+  explicit constexpr __no_unique_address_emulation(_T const& __v) noexcept : _T(__v) {}
+  MDSPAN_INLINE_FUNCTION
+  explicit constexpr __no_unique_address_emulation(_T&& __v) noexcept : _T(::std::move(__v)) {}
+};
+
+//==============================================================================
+
+} // end namespace detail
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/trait_backports.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/trait_backports.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4933dd9934ea0a967ff46b5afc303a1431df6748
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/trait_backports.hpp
@@ -0,0 +1,132 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_
+#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_
+
+#include "macros.hpp"
+#include "config.hpp"
+
+#include <type_traits>
+#include <utility> // integer_sequence
+
+//==============================================================================
+// <editor-fold desc="Variable template trait backports (e.g., is_void_v)"> {{{1
+
+#ifdef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS
+
+#if _MDSPAN_USE_VARIABLE_TEMPLATES
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+#define _MDSPAN_BACKPORT_TRAIT(TRAIT) \
+  template <class... Args> _MDSPAN_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT<Args...>::value;
+
+_MDSPAN_BACKPORT_TRAIT(is_assignable)
+_MDSPAN_BACKPORT_TRAIT(is_constructible)
+_MDSPAN_BACKPORT_TRAIT(is_convertible)
+_MDSPAN_BACKPORT_TRAIT(is_default_constructible)
+_MDSPAN_BACKPORT_TRAIT(is_trivially_destructible)
+_MDSPAN_BACKPORT_TRAIT(is_same)
+_MDSPAN_BACKPORT_TRAIT(is_empty)
+_MDSPAN_BACKPORT_TRAIT(is_void)
+
+#undef _MDSPAN_BACKPORT_TRAIT
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#endif // _MDSPAN_USE_VARIABLE_TEMPLATES
+
+#endif // _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS
+
+// </editor-fold> end Variable template trait backports (e.g., is_void_v) }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="integer sequence (ugh...)"> {{{1
+
+#if !defined(_MDSPAN_USE_INTEGER_SEQUENCE) || !_MDSPAN_USE_INTEGER_SEQUENCE
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+template <class T, T... Vals>
+struct integer_sequence {
+  static constexpr size_t size() noexcept { return sizeof...(Vals); }
+  using value_type = T;
+};
+
+template <size_t... Vals>
+using index_sequence = std::integer_sequence<size_t, Vals...>;
+
+namespace __detail {
+
+template <class T, T N, T I, class Result>
+struct __make_int_seq_impl;
+
+template <class T, T N, T... Vals>
+struct __make_int_seq_impl<T, N, N, integer_sequence<T, Vals...>>
+{
+  using type = integer_sequence<T, Vals...>;
+};
+
+template <class T, T N, T I, T... Vals>
+struct __make_int_seq_impl<
+  T, N, I, integer_sequence<T, Vals...>
+> : __make_int_seq_impl<T, N, I+1, integer_sequence<T, Vals..., I>>
+{ };
+
+} // end namespace __detail
+
+template <class T, T N>
+using make_integer_sequence = typename __detail::__make_int_seq_impl<T, N, 0, integer_sequence<T>>::type;
+
+template <size_t N>
+using make_index_sequence = typename __detail::__make_int_seq_impl<size_t, N, 0, integer_sequence<size_t>>::type;
+
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#endif
+
+// </editor-fold> end integer sequence (ugh...) }}}1
+//==============================================================================
+
+//==============================================================================
+// <editor-fold desc="standard trait aliases"> {{{1
+
+#if !defined(_MDSPAN_USE_STANDARD_TRAIT_ALIASES) || !_MDSPAN_USE_STANDARD_TRAIT_ALIASES
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+#define _MDSPAN_BACKPORT_TRAIT_ALIAS(TRAIT) \
+  template <class... Args> using TRAIT##_t = typename TRAIT<Args...>::type;
+
+_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_cv)
+_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_reference)
+
+template <bool _B, class _T=void>
+using enable_if_t = typename enable_if<_B, _T>::type;
+
+#undef _MDSPAN_BACKPORT_TRAIT_ALIAS
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#endif
+
+// </editor-fold> end standard trait aliases }}}1
+//==============================================================================
+
+#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/type_list.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/type_list.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..deca7c15d095857a805fa00cbd1947ce8c434d65
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/type_list.hpp
@@ -0,0 +1,87 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#include "macros.hpp"
+
+#include "trait_backports.hpp" // make_index_sequence
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+//==============================================================================
+
+namespace detail {
+
+template <class... _Ts> struct __type_list { static constexpr auto __size = sizeof...(_Ts); };
+
+// Implementation of type_list at() that's heavily optimized for small typelists
+template <size_t, class> struct __type_at;
+template <size_t, class _Seq, class=std::make_index_sequence<_Seq::__size>> struct __type_at_large_impl;
+
+template <size_t _I, size_t _Idx, class _T>
+struct __type_at_entry { };
+
+template <class _Result>
+struct __type_at_assign_op_ignore_rest {
+  template <class _T>
+  __type_at_assign_op_ignore_rest<_Result> operator=(_T&&);
+  using type = _Result;
+};
+
+struct __type_at_assign_op_impl {
+  template <size_t _I, size_t _Idx, class _T>
+  __type_at_assign_op_impl operator=(__type_at_entry<_I, _Idx, _T>&&);
+  template <size_t _I, class _T>
+  __type_at_assign_op_ignore_rest<_T> operator=(__type_at_entry<_I, _I, _T>&&);
+};
+
+template <size_t _I, class... _Ts, size_t... _Idxs>
+struct __type_at_large_impl<_I, __type_list<_Ts...>, std::integer_sequence<size_t, _Idxs...>>
+  : decltype(
+      _MDSPAN_FOLD_ASSIGN_LEFT(__type_at_assign_op_impl{}, /* = ... = */ __type_at_entry<_I, _Idxs, _Ts>{})
+    )
+{ };
+
+template <size_t _I, class... _Ts>
+struct __type_at<_I, __type_list<_Ts...>>
+    : __type_at_large_impl<_I, __type_list<_Ts...>>
+{ };
+
+template <class _T0, class... _Ts>
+struct __type_at<0, __type_list<_T0, _Ts...>> {
+  using type = _T0;
+};
+
+template <class _T0, class _T1, class... _Ts>
+struct __type_at<1, __type_list<_T0, _T1, _Ts...>> {
+  using type = _T1;
+};
+
+template <class _T0, class _T1, class _T2, class... _Ts>
+struct __type_at<2, __type_list<_T0, _T1, _T2, _Ts...>> {
+  using type = _T2;
+};
+
+template <class _T0, class _T1, class _T2, class _T3, class... _Ts>
+struct __type_at<3, __type_list<_T0, _T1, _T2, _T3, _Ts...>> {
+  using type = _T3;
+};
+
+
+} // namespace detail
+
+//==============================================================================
+
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
diff --git a/tests/integration/deps/mdspan/include/experimental/__p0009_bits/utility.hpp b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/utility.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca821176f875e4e650984a4d674e0a64dede53af
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p0009_bits/utility.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+// type alias used for rank-based tag dispatch
+//
+// this is used to enable alternatives to constexpr if when building for C++14
+//
+template <std::size_t N>
+using with_rank = std::integral_constant<std::size_t, N>;
+
+template <class I1, class I2>
+constexpr bool common_integral_compare(I1 x, I2 y)
+{
+  static_assert(std::is_integral<I1>::value and
+                std::is_integral<I2>::value, "");
+
+  using I = std::common_type_t<I1, I2>;
+  return static_cast<I>(x) == static_cast<I>(y);
+}
+
+template <class T1, class T2, class F>
+constexpr bool rankwise_equal(with_rank<0>, const T1&, const T2&, F)
+{
+  return true;
+}
+template <std::size_t N, class T1, class T2, class F>
+constexpr bool rankwise_equal(with_rank<N>, const T1& x, const T2& y, F func)
+{
+  bool match = true;
+
+  for (std::size_t r = 0; r < N; r++) {
+    match = match && common_integral_compare(func(x, r), func(y, r));
+  }
+
+  return match;
+}
+
+constexpr struct
+{
+  template <class T, class I>
+  constexpr auto operator()(const T& x, I i) const
+  {
+    return x.extent(i);
+  }
+} extent;
+
+constexpr struct
+{
+  template <class T, class I>
+  constexpr auto operator()(const T& x, I i) const
+  {
+    return x.stride(i);
+  }
+} stride;
+
+} // namespace detail
+
+constexpr struct mdspan_non_standard_tag {
+} mdspan_non_standard;
+
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p1684_bits/mdarray.hpp b/tests/integration/deps/mdspan/include/experimental/__p1684_bits/mdarray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bdc5925f715190b2d7163ec75e0c5956df706df9
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p1684_bits/mdarray.hpp
@@ -0,0 +1,460 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include "../mdspan"
+#include <cassert>
+#include <vector>
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace MDSPAN_IMPL_PROPOSED_NAMESPACE {
+
+namespace {
+  template<class Extents>
+  struct size_of_extents;
+
+  template<class IndexType, size_t ... Extents>
+  struct size_of_extents<extents<IndexType, Extents...>> {
+    constexpr static size_t value() {
+      size_t size = 1;
+      for(size_t r=0; r<extents<IndexType, Extents...>::rank(); r++)
+        size *= extents<IndexType, Extents...>::static_extent(r);
+      return size;
+    }
+  };
+}
+
+namespace {
+  template<class C>
+  struct container_is_array :  std::false_type {
+    template<class M>
+    static constexpr C construct(const M& m) { return C(m.required_span_size()); }
+  };
+  template<class T, size_t N>
+  struct container_is_array<std::array<T,N>> : std::true_type {
+    template<class M>
+    static constexpr std::array<T,N> construct(const M&) { return std::array<T,N>(); }
+  };
+}
+
+template <
+  class ElementType,
+  class Extents,
+  class LayoutPolicy = layout_right,
+  class Container = std::vector<ElementType>
+>
+class mdarray {
+private:
+  static_assert(::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::__is_extents_v<Extents>,
+                MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents.");
+
+public:
+
+  //--------------------------------------------------------------------------------
+  // Domain and codomain types
+
+  using extents_type = Extents;
+  using layout_type = LayoutPolicy;
+  using container_type = Container;
+  using mapping_type = typename layout_type::template mapping<extents_type>;
+  using element_type = ElementType;
+  using mdspan_type = mdspan<element_type, extents_type, layout_type>;
+  using const_mdspan_type = mdspan<const element_type, extents_type, layout_type>;
+  using value_type = std::remove_cv_t<element_type>;
+  using index_type = typename Extents::index_type;
+  using size_type = typename Extents::size_type;
+  using rank_type = typename Extents::rank_type;
+  using pointer = typename container_type::pointer;
+  using reference = typename container_type::reference;
+  using const_pointer = typename container_type::const_pointer;
+  using const_reference = typename container_type::const_reference;
+
+public:
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.cons], mdspan constructors, assignment, and destructor
+
+#if !(MDSPAN_HAS_CXX_20)
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr),
+    mdarray, (), ,
+    /* requires */ (extents_type::rank_dynamic()!=0)) {}
+#else
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray() requires(extents_type::rank_dynamic()!=0) = default;
+#endif
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(const mdarray&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray(mdarray&&) = default;
+
+  // Constructors for container types constructible from a size
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices<index_type, SizeTypes...>()) &&
+        _MDSPAN_TRAIT( std::is_constructible, extents_type, SizeTypes...) &&
+      _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type) &&
+      (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) ||
+       container_is_array<container_type>::value) &&
+      (extents_type::rank()>0 || extents_type::rank_dynamic()==0)
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  explicit constexpr mdarray(SizeTypes... dynamic_extents)
+    : map_(extents_type(dynamic_extents...)), ctr_(container_is_array<container_type>::construct(map_))
+  { }
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdarray, (const extents_type& exts), ,
+    /* requires */ ((_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) ||
+                     container_is_array<container_type>::value) &&
+                    _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  ) : map_(exts), ctr_(container_is_array<container_type>::construct(map_))
+  { }
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdarray, (const mapping_type& m), ,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t) ||
+                    container_is_array<container_type>::value)
+  ) : map_(m), ctr_(container_is_array<container_type>::construct(map_))
+  { }
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdarray, (const extents_type& exts, const container_type& ctr), ,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  ) : map_(exts), ctr_(ctr)
+  { assert(ctr.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  constexpr mdarray(const mapping_type& m, const container_type& ctr)
+    : map_(m), ctr_(ctr)
+  { assert(ctr.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  MDSPAN_FUNCTION_REQUIRES(
+    (MDSPAN_INLINE_FUNCTION constexpr),
+    mdarray, (const extents_type& exts, container_type&& ctr), ,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  ) : map_(exts), ctr_(std::move(ctr))
+  { assert(ctr_.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  constexpr mdarray(const mapping_type& m, container_type&& ctr)
+    : map_(m), ctr_(std::move(ctr))
+  { assert(ctr_.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping<OtherExtents>) &&
+      _MDSPAN_TRAIT( std::is_constructible, container_type, OtherContainer)
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const mdarray<OtherElementType, OtherExtents, OtherLayoutPolicy, OtherContainer>& other)
+    : map_(other.mapping()), ctr_(other.container())
+  {
+    static_assert( std::is_constructible<extents_type, OtherExtents>::value, "");
+  }
+
+  // Constructors for container types constructible from a size and allocator
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc) &&
+                    _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const extents_type& exts, const Alloc& a)
+    : map_(exts), ctr_(map_.required_span_size(), a)
+  { }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const mapping_type& map, const Alloc& a)
+    : map_(map), ctr_(map_.required_span_size(), a)
+  { }
+
+  // Constructors for container types constructible from a container and allocator
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, container_type, Alloc) &&
+                    _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const extents_type& exts, const container_type& ctr, const Alloc& a)
+    : map_(exts), ctr_(ctr, a)
+  { assert(ctr_.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const mapping_type& map, const container_type& ctr, const Alloc& a)
+    : map_(map), ctr_(ctr, a)
+  { assert(ctr_.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, container_type, Alloc) &&
+                    _MDSPAN_TRAIT( std::is_constructible, mapping_type, extents_type))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const extents_type& exts, container_type&& ctr, const Alloc& a)
+    : map_(exts), ctr_(std::move(ctr), a)
+  { assert(ctr_.size() >= static_cast<size_t>(map_.required_span_size())); }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Alloc,
+    /* requires */ (_MDSPAN_TRAIT( std::is_constructible, container_type, size_t, Alloc))
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const mapping_type& map, container_type&& ctr, const Alloc& a)
+    : map_(map), ctr_(std::move(ctr), a)
+  { assert(ctr_.size() >= map_.required_span_size()); }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherContainer, class Alloc,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_constructible, mapping_type, typename OtherLayoutPolicy::template mapping<OtherExtents>) &&
+      _MDSPAN_TRAIT( std::is_constructible, container_type, OtherContainer, Alloc)
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mdarray(const mdarray<OtherElementType, OtherExtents, OtherLayoutPolicy, OtherContainer>& other, const Alloc& a)
+    : map_(other.mapping()), ctr_(other.container(), a)
+  {
+    static_assert( std::is_constructible<extents_type, OtherExtents>::value, "");
+  }
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (const mdarray&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdarray& operator= (mdarray&&) = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  ~mdarray() = default;
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element
+
+  #if MDSPAN_USE_BRACKET_OPERATOR
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) &&
+      extents_type::rank() == sizeof...(SizeTypes)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr const_reference operator[](SizeTypes... indices) const noexcept
+  {
+    return ctr_[map_(static_cast<index_type>(std::move(indices))...)];
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+      _MDSPAN_FOLD_AND(_MDSPAN_TRAIT( std::is_convertible, SizeTypes, index_type) /* && ... */) &&
+      extents_type::rank() == sizeof...(SizeTypes)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](SizeTypes... indices) noexcept
+  {
+    return ctr_[map_(static_cast<index_type>(std::move(indices))...)];
+  }
+  #endif
+
+#if 0
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) &&
+      N == extents_type::rank()
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr const_reference operator[](const std::array<SizeType, N>& indices) const noexcept
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) &&
+      N == extents_type::rank()
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator[](const std::array<SizeType, N>& indices) noexcept
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+#endif
+
+
+  #if MDSPAN_USE_PAREN_OPERATOR
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+        (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices<index_type, SizeTypes...>()) &&
+        extents_type::rank() == sizeof...(SizeTypes)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr const_reference operator()(SizeTypes... indices) const noexcept
+  {
+    return ctr_[map_(static_cast<index_type>(std::move(indices))...)];
+  }
+  MDSPAN_TEMPLATE_REQUIRES(
+    class... SizeTypes,
+    /* requires */ (
+        (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::are_valid_indices<index_type, SizeTypes...>()) &&
+        extents_type::rank() == sizeof...(SizeTypes)
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator()(SizeTypes... indices) noexcept
+  {
+    return ctr_[map_(static_cast<index_type>(std::move(indices))...)];
+  }
+
+#if 0
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) &&
+      N == extents_type::rank()
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr const_reference operator()(const std::array<SizeType, N>& indices) const noexcept
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class SizeType, size_t N,
+    /* requires */ (
+      _MDSPAN_TRAIT( std::is_convertible, SizeType, index_type) &&
+      N == extents_type::rank()
+    )
+  )
+  MDSPAN_FORCE_INLINE_FUNCTION
+  constexpr reference operator()(const std::array<SizeType, N>& indices) noexcept
+  {
+    return __impl::template __callop<reference>(*this, indices);
+  }
+#endif
+  #endif
+
+  MDSPAN_INLINE_FUNCTION constexpr pointer data() noexcept { return ctr_.data(); };
+  MDSPAN_INLINE_FUNCTION constexpr const_pointer data() const noexcept { return ctr_.data(); };
+  MDSPAN_INLINE_FUNCTION constexpr container_type& container() noexcept { return ctr_; };
+  MDSPAN_INLINE_FUNCTION constexpr const container_type& container() const noexcept { return ctr_; };
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space
+
+  MDSPAN_INLINE_FUNCTION static constexpr rank_type rank() noexcept { return extents_type::rank(); }
+  MDSPAN_INLINE_FUNCTION static constexpr rank_type rank_dynamic() noexcept { return extents_type::rank_dynamic(); }
+  MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); }
+
+  MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return map_.extents(); };
+  MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return map_.extents().extent(r); };
+  MDSPAN_INLINE_FUNCTION constexpr index_type size() const noexcept {
+//    return __impl::__size(*this);
+    return ctr_.size();
+  };
+
+
+  //--------------------------------------------------------------------------------
+  // [mdspan.basic.obs], mdspan observers of the mapping
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return mapping_type::is_always_unique(); };
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return mapping_type::is_always_exhaustive(); };
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return mapping_type::is_always_strided(); };
+
+  MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return map_; };
+  MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const noexcept { return map_.is_unique(); };
+  MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { return map_.is_exhaustive(); };
+  MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const noexcept { return map_.is_strided(); };
+  MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return map_.stride(r); };
+
+  // Converstion to mdspan
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType, class OtherExtents,
+    class OtherLayoutType, class OtherAccessorType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_assignable,
+                      mdspan<OtherElementType, OtherExtents, OtherLayoutType, OtherAccessorType>,
+                      mdspan_type)
+    )
+  )
+  constexpr operator mdspan<OtherElementType, OtherExtents, OtherLayoutType, OtherAccessorType> () {
+    return mdspan_type(data(), map_);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherElementType, class OtherExtents,
+    class OtherLayoutType, class OtherAccessorType,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_assignable,
+                      mdspan<OtherElementType, OtherExtents, OtherLayoutType, OtherAccessorType>,
+                      const_mdspan_type)
+    )
+  )
+  constexpr operator mdspan<OtherElementType, OtherExtents, OtherLayoutType, OtherAccessorType> () const {
+    return const_mdspan_type(data(), map_);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherAccessorType = default_accessor<element_type>,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_assignable, mdspan_type,
+                      mdspan<element_type, extents_type, layout_type, OtherAccessorType>)
+    )
+  )
+  constexpr mdspan<element_type, extents_type, layout_type, OtherAccessorType>
+    to_mdspan(const OtherAccessorType& a = default_accessor<element_type>()) {
+      return mdspan<element_type, extents_type, layout_type, OtherAccessorType>(data(), map_, a);
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class OtherAccessorType = default_accessor<const element_type>,
+    /* requires */ (
+      _MDSPAN_TRAIT(std::is_assignable, const_mdspan_type,
+                      mdspan<const element_type, extents_type, layout_type, OtherAccessorType>)
+    )
+  )
+  constexpr mdspan<const element_type, extents_type, layout_type, OtherAccessorType>
+    to_mdspan(const OtherAccessorType& a = default_accessor<const element_type>()) const {
+      return mdspan<const element_type, extents_type, layout_type, OtherAccessorType>(data(), map_, a);
+  }
+
+private:
+  mapping_type map_;
+  container_type ctr_;
+
+  template <class, class, class, class>
+  friend class mdarray;
+};
+
+
+} // end namespace MDSPAN_IMPL_PROPOSED_NAMESPACE
+} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2389_bits/dims.hpp b/tests/integration/deps/mdspan/include/experimental/__p2389_bits/dims.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..00045215c489bfaa04bd0d7967a85b49559fb714
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2389_bits/dims.hpp
@@ -0,0 +1,28 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+// backward compatibility import into experimental
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace MDSPAN_IMPL_PROPOSED_NAMESPACE {
+
+template< ::std::size_t Rank, class IndexType = std::size_t>
+using dims =
+  :: MDSPAN_IMPL_STANDARD_NAMESPACE :: dextents<IndexType, Rank>;
+
+} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2630_bits/strided_slice.hpp b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/strided_slice.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..89ba8202fb16a090bfa9352a4dcf7041cf53c90f
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/strided_slice.hpp
@@ -0,0 +1,48 @@
+
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include <type_traits>
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+
+namespace {
+  template<class T>
+  struct __mdspan_is_integral_constant: std::false_type {};
+
+  template<class T, T val>
+  struct __mdspan_is_integral_constant<std::integral_constant<T,val>>: std::true_type {};
+}
+
+// Slice Specifier allowing for strides and compile time extent
+template <class OffsetType, class ExtentType, class StrideType>
+struct strided_slice {
+  using offset_type = OffsetType;
+  using extent_type = ExtentType;
+  using stride_type = StrideType;
+
+  _MDSPAN_NO_UNIQUE_ADDRESS OffsetType offset{};
+  _MDSPAN_NO_UNIQUE_ADDRESS ExtentType extent{};
+  _MDSPAN_NO_UNIQUE_ADDRESS StrideType stride{};
+
+  static_assert(std::is_integral_v<OffsetType> || __mdspan_is_integral_constant<OffsetType>::value);
+  static_assert(std::is_integral_v<ExtentType> || __mdspan_is_integral_constant<ExtentType>::value);
+  static_assert(std::is_integral_v<StrideType> || __mdspan_is_integral_constant<StrideType>::value);
+};
+
+} // MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan.hpp b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..abddd0b59df170f2b16f7e5d301e45378a42bdee
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan.hpp
@@ -0,0 +1,40 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include "submdspan_extents.hpp"
+#include "submdspan_mapping.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+template <class ElementType, class Extents, class LayoutPolicy,
+          class AccessorPolicy, class... SliceSpecifiers>
+MDSPAN_INLINE_FUNCTION
+constexpr auto
+submdspan(const mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy> &src,
+          SliceSpecifiers... slices) {
+  const auto sub_submdspan_mapping_result = submdspan_mapping(src.mapping(), slices...);
+  // NVCC has a problem with the deduction so lets figure out the type
+  using sub_mapping_t = std::remove_cv_t<decltype(sub_submdspan_mapping_result.mapping)>;
+  using sub_extents_t = typename sub_mapping_t::extents_type;
+  using sub_layout_t = typename sub_mapping_t::layout_type;
+  using sub_accessor_t = typename AccessorPolicy::offset_policy;
+  return mdspan<ElementType, sub_extents_t, sub_layout_t, sub_accessor_t>(
+      src.accessor().offset(src.data_handle(), sub_submdspan_mapping_result.offset),
+      sub_submdspan_mapping_result.mapping,
+      sub_accessor_t(src.accessor()));
+}
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3b2f78fb998cb1a3c77a9cac8738085d81cbbaf
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp
@@ -0,0 +1,321 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include <tuple>
+
+#include "strided_slice.hpp"
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace detail {
+
+// Mapping from submapping ranks to srcmapping ranks
+// InvMapRank is an index_sequence, which we build recursively
+// to contain the mapped indices.
+// end of recursion specialization containing the final index_sequence
+template <size_t Counter, size_t... MapIdxs>
+MDSPAN_INLINE_FUNCTION
+constexpr auto inv_map_rank(std::integral_constant<size_t, Counter>, std::index_sequence<MapIdxs...>) {
+  return std::index_sequence<MapIdxs...>();
+}
+
+// specialization reducing rank by one (i.e., integral slice specifier)
+template<size_t Counter, class Slice, class... SliceSpecifiers, size_t... MapIdxs>
+MDSPAN_INLINE_FUNCTION
+constexpr auto inv_map_rank(std::integral_constant<size_t, Counter>, std::index_sequence<MapIdxs...>, Slice,
+                  SliceSpecifiers... slices) {
+  using next_idx_seq_t = std::conditional_t<std::is_convertible_v<Slice, size_t>,
+                                       std::index_sequence<MapIdxs...>,
+                                       std::index_sequence<MapIdxs..., Counter>>;
+
+  return inv_map_rank(std::integral_constant<size_t,Counter + 1>(), next_idx_seq_t(),
+                                     slices...);
+}
+
+// Helper for identifying strided_slice
+template <class T> struct is_strided_slice : std::false_type {};
+
+template <class OffsetType, class ExtentType, class StrideType>
+struct is_strided_slice<
+    strided_slice<OffsetType, ExtentType, StrideType>> : std::true_type {};
+
+// first_of(slice): getting begin of slice specifier range
+MDSPAN_TEMPLATE_REQUIRES(
+  class Integral,
+  /* requires */(std::is_convertible_v<Integral, size_t>)
+)
+MDSPAN_INLINE_FUNCTION
+constexpr Integral first_of(const Integral &i) {
+  return i;
+}
+
+MDSPAN_INLINE_FUNCTION
+constexpr std::integral_constant<size_t, 0>
+first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) {
+  return std::integral_constant<size_t, 0>();
+}
+
+MDSPAN_TEMPLATE_REQUIRES(
+  class Slice,
+  /* requires */(std::is_convertible_v<Slice, std::tuple<size_t, size_t>>)
+)
+MDSPAN_INLINE_FUNCTION
+constexpr auto first_of(const Slice &i) {
+  return std::get<0>(i);
+}
+
+template <class OffsetType, class ExtentType, class StrideType>
+MDSPAN_INLINE_FUNCTION
+constexpr OffsetType
+first_of(const strided_slice<OffsetType, ExtentType, StrideType> &r) {
+  return r.offset;
+}
+
+// last_of(slice): getting end of slice specifier range
+// We need however not just the slice but also the extents
+// of the original view and which rank from the extents.
+// This is needed in the case of slice being full_extent_t.
+MDSPAN_TEMPLATE_REQUIRES(
+  size_t k, class Extents, class Integral,
+  /* requires */(std::is_convertible_v<Integral, size_t>)
+)
+MDSPAN_INLINE_FUNCTION
+constexpr Integral
+    last_of(std::integral_constant<size_t, k>, const Extents &, const Integral &i) {
+  return i;
+}
+
+MDSPAN_TEMPLATE_REQUIRES(
+  size_t k, class Extents, class Slice,
+  /* requires */(std::is_convertible_v<Slice, std::tuple<size_t, size_t>>)
+)
+MDSPAN_INLINE_FUNCTION
+constexpr auto last_of(std::integral_constant<size_t, k>, const Extents &,
+                       const Slice &i) {
+  return std::get<1>(i);
+}
+
+// Suppress spurious warning with NVCC about no return statement.
+// This is a known issue in NVCC and NVC++
+// Depending on the CUDA and GCC version we need both the builtin
+// and the diagnostic push. I tried really hard to find something shorter
+// but no luck ...
+#if defined __NVCC__
+    #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
+        #pragma nv_diagnostic push
+        #pragma nv_diag_suppress = implicit_return_from_non_void_function
+    #else
+      #ifdef __CUDA_ARCH__
+        #pragma diagnostic push
+        #pragma diag_suppress implicit_return_from_non_void_function
+      #endif
+    #endif
+#elif defined __NVCOMPILER
+    #pragma    diagnostic push
+    #pragma    diag_suppress = implicit_return_from_non_void_function
+#endif
+template <size_t k, class Extents>
+MDSPAN_INLINE_FUNCTION
+constexpr auto last_of(std::integral_constant<size_t, k>, const Extents &ext,
+                       ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) {
+  if constexpr (Extents::static_extent(k) == dynamic_extent) {
+    return ext.extent(k);
+  } else {
+    return std::integral_constant<size_t, Extents::static_extent(k)>();
+  }
+#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__)
+  // Even with CUDA_ARCH protection this thing warns about calling host function
+  __builtin_unreachable();
+#endif
+}
+#if defined __NVCC__
+    #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
+        #pragma nv_diagnostic pop
+    #else
+      #ifdef __CUDA_ARCH__
+        #pragma diagnostic pop
+      #endif
+    #endif
+#elif defined __NVCOMPILER
+    #pragma    diagnostic pop
+#endif
+
+template <size_t k, class Extents, class OffsetType, class ExtentType,
+          class StrideType>
+MDSPAN_INLINE_FUNCTION
+constexpr OffsetType
+last_of(std::integral_constant<size_t, k>, const Extents &,
+        const strided_slice<OffsetType, ExtentType, StrideType> &r) {
+  return r.extent;
+}
+
+// get stride of slices
+template <class T>
+MDSPAN_INLINE_FUNCTION
+constexpr auto stride_of(const T &) {
+  return std::integral_constant<size_t, 1>();
+}
+
+template <class OffsetType, class ExtentType, class StrideType>
+MDSPAN_INLINE_FUNCTION
+constexpr auto
+stride_of(const strided_slice<OffsetType, ExtentType, StrideType> &r) {
+  return r.stride;
+}
+
+// divide which can deal with integral constant preservation
+template <class IndexT, class T0, class T1>
+MDSPAN_INLINE_FUNCTION
+constexpr auto divide(const T0 &v0, const T1 &v1) {
+  return IndexT(v0) / IndexT(v1);
+}
+
+template <class IndexT, class T0, T0 v0, class T1, T1 v1>
+MDSPAN_INLINE_FUNCTION
+constexpr auto divide(const std::integral_constant<T0, v0> &,
+                      const std::integral_constant<T1, v1> &) {
+  // cutting short division by zero
+  // this is used for strided_slice with zero extent/stride
+  return std::integral_constant<IndexT, v0 == 0 ? 0 : v0 / v1>();
+}
+
+// multiply which can deal with integral constant preservation
+template <class IndexT, class T0, class T1>
+MDSPAN_INLINE_FUNCTION
+constexpr auto multiply(const T0 &v0, const T1 &v1) {
+  return IndexT(v0) * IndexT(v1);
+}
+
+template <class IndexT, class T0, T0 v0, class T1, T1 v1>
+MDSPAN_INLINE_FUNCTION
+constexpr auto multiply(const std::integral_constant<T0, v0> &,
+                        const std::integral_constant<T1, v1> &) {
+  return std::integral_constant<IndexT, v0 * v1>();
+}
+
+// compute new static extent from range, preserving static knowledge
+template <class Arg0, class Arg1> struct StaticExtentFromRange {
+  constexpr static size_t value = dynamic_extent;
+};
+
+template <class Integral0, Integral0 val0, class Integral1, Integral1 val1>
+struct StaticExtentFromRange<std::integral_constant<Integral0, val0>,
+                             std::integral_constant<Integral1, val1>> {
+  constexpr static size_t value = val1 - val0;
+};
+
+// compute new static extent from strided_slice, preserving static
+// knowledge
+template <class Arg0, class Arg1> struct StaticExtentFromStridedRange {
+  constexpr static size_t value = dynamic_extent;
+};
+
+template <class Integral0, Integral0 val0, class Integral1, Integral1 val1>
+struct StaticExtentFromStridedRange<std::integral_constant<Integral0, val0>,
+                                    std::integral_constant<Integral1, val1>> {
+  constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0;
+};
+
+// creates new extents through recursive calls to next_extent member function
+// next_extent has different overloads for different types of stride specifiers
+template <size_t K, class Extents, size_t... NewExtents>
+struct extents_constructor {
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Slice, class... SlicesAndExtents,
+    /* requires */(!std::is_convertible_v<Slice, size_t> &&
+                   !is_strided_slice<Slice>::value)
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr static auto next_extent(const Extents &ext, const Slice &sl,
+                                    SlicesAndExtents... slices_and_extents) {
+    constexpr size_t new_static_extent = StaticExtentFromRange<
+        decltype(first_of(std::declval<Slice>())),
+        decltype(last_of(std::integral_constant<size_t, Extents::rank() - K>(),
+                         std::declval<Extents>(),
+                         std::declval<Slice>()))>::value;
+
+    using next_t =
+        extents_constructor<K - 1, Extents, NewExtents..., new_static_extent>;
+    using index_t = typename Extents::index_type;
+    return next_t::next_extent(
+        ext, slices_and_extents...,
+        index_t(last_of(std::integral_constant<size_t, Extents::rank() - K>(), ext,
+                        sl)) -
+            index_t(first_of(sl)));
+  }
+
+  MDSPAN_TEMPLATE_REQUIRES(
+    class Slice, class... SlicesAndExtents,
+    /* requires */ (std::is_convertible_v<Slice, size_t>)
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr static auto next_extent(const Extents &ext, const Slice &,
+                                    SlicesAndExtents... slices_and_extents) {
+    using next_t = extents_constructor<K - 1, Extents, NewExtents...>;
+    return next_t::next_extent(ext, slices_and_extents...);
+  }
+
+  template <class OffsetType, class ExtentType, class StrideType,
+            class... SlicesAndExtents>
+  MDSPAN_INLINE_FUNCTION
+  constexpr static auto
+  next_extent(const Extents &ext,
+              const strided_slice<OffsetType, ExtentType, StrideType> &r,
+              SlicesAndExtents... slices_and_extents) {
+    using index_t = typename Extents::index_type;
+    using new_static_extent_t =
+        StaticExtentFromStridedRange<ExtentType, StrideType>;
+    if constexpr (new_static_extent_t::value == dynamic_extent) {
+      using next_t =
+          extents_constructor<K - 1, Extents, NewExtents..., dynamic_extent>;
+      return next_t::next_extent(
+          ext, slices_and_extents...,
+          r.extent > 0 ? 1 + divide<index_t>(r.extent - 1, r.stride) : 0);
+    } else {
+      constexpr size_t new_static_extent = new_static_extent_t::value;
+      using next_t =
+          extents_constructor<K - 1, Extents, NewExtents..., new_static_extent>;
+      return next_t::next_extent(
+          ext, slices_and_extents..., index_t(divide<index_t>(ExtentType(), StrideType())));
+    }
+  }
+};
+
+template <class Extents, size_t... NewStaticExtents>
+struct extents_constructor<0, Extents, NewStaticExtents...> {
+
+  template <class... NewExtents>
+  MDSPAN_INLINE_FUNCTION
+  constexpr static auto next_extent(const Extents &, NewExtents... new_exts) {
+    return extents<typename Extents::index_type, NewStaticExtents...>(
+        new_exts...);
+  }
+};
+
+} // namespace detail
+
+// submdspan_extents creates new extents given src extents and submdspan slice
+// specifiers
+template <class IndexType, size_t... Extents, class... SliceSpecifiers>
+MDSPAN_INLINE_FUNCTION
+constexpr auto submdspan_extents(const extents<IndexType, Extents...> &src_exts,
+                                 SliceSpecifiers... slices) {
+
+  using ext_t = extents<IndexType, Extents...>;
+  return detail::extents_constructor<ext_t::rank(), ext_t>::next_extent(
+      src_exts, slices...);
+}
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf1bdd1e56f2b1ec356de554fe0cf8685aa76ded
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp
@@ -0,0 +1,452 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#include <array>
+#include <tuple>
+#include <type_traits>
+#include <utility> // index_sequence
+
+// Suppress spurious warning with NVCC about no return statement.
+// This is a known issue in NVCC and NVC++
+// Depending on the CUDA and GCC version we need both the builtin
+// and the diagnostic push. I tried really hard to find something shorter
+// but no luck ...
+#if defined __NVCC__
+#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
+#pragma nv_diagnostic push
+#pragma nv_diag_suppress = implicit_return_from_non_void_function
+#else
+#ifdef __CUDA_ARCH__
+#pragma diagnostic push
+#pragma diag_suppress implicit_return_from_non_void_function
+#endif
+#endif
+#elif defined __NVCOMPILER
+#pragma diagnostic push
+#pragma diag_suppress = implicit_return_from_non_void_function
+#endif
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+//******************************************
+// Return type of submdspan_mapping overloads
+//******************************************
+template <class LayoutMapping> struct submdspan_mapping_result {
+  _MDSPAN_NO_UNIQUE_ADDRESS LayoutMapping mapping{};
+  size_t offset;
+};
+
+namespace detail {
+// We use const Slice& and not Slice&& because the various
+// submdspan_mapping_impl overloads use their slices arguments
+// multiple times.  This makes perfect forwarding not useful, but we
+// still don't want to pass those (possibly of size 64 x 3 bits)
+// objects by value.
+template <class IndexType, class Slice>
+MDSPAN_INLINE_FUNCTION constexpr bool
+one_slice_out_of_bounds(const IndexType &ext, const Slice &slice) {
+  using common_t =
+      std::common_type_t<decltype(detail::first_of(slice)), IndexType>;
+  return static_cast<common_t>(detail::first_of(slice)) ==
+         static_cast<common_t>(ext);
+}
+
+template <size_t... RankIndices, class IndexType, size_t... Exts,
+          class... Slices>
+MDSPAN_INLINE_FUNCTION constexpr bool
+any_slice_out_of_bounds_helper(std::index_sequence<RankIndices...>,
+                               const extents<IndexType, Exts...> &exts,
+                               const Slices &... slices) {
+  return _MDSPAN_FOLD_OR(
+      (one_slice_out_of_bounds(exts.extent(RankIndices), slices)));
+}
+
+template <class IndexType, size_t... Exts, class... Slices>
+MDSPAN_INLINE_FUNCTION constexpr bool
+any_slice_out_of_bounds(const extents<IndexType, Exts...> &exts,
+                        const Slices &... slices) {
+  return any_slice_out_of_bounds_helper(
+      std::make_index_sequence<sizeof...(Slices)>(), exts, slices...);
+}
+
+// constructs sub strides
+template <class SrcMapping, class... slice_strides, size_t... InvMapIdxs>
+MDSPAN_INLINE_FUNCTION constexpr auto construct_sub_strides(
+    const SrcMapping &src_mapping, std::index_sequence<InvMapIdxs...>,
+    const std::tuple<slice_strides...> &slices_stride_factor) {
+  using index_type = typename SrcMapping::index_type;
+  return std::array<typename SrcMapping::index_type, sizeof...(InvMapIdxs)>{
+      (static_cast<index_type>(src_mapping.stride(InvMapIdxs)) *
+       static_cast<index_type>(std::get<InvMapIdxs>(slices_stride_factor)))...};
+}
+
+template<class SliceSpecifier, class IndexType>
+struct is_range_slice {
+  constexpr static bool value =
+    std::is_same_v<SliceSpecifier, full_extent_t> ||
+    std::is_convertible_v<SliceSpecifier,
+                          std::tuple<IndexType, IndexType>>;
+};
+
+template<class SliceSpecifier, class IndexType>
+constexpr bool is_range_slice_v = is_range_slice<SliceSpecifier, IndexType>::value;
+
+template<class SliceSpecifier, class IndexType>
+struct is_index_slice {
+  constexpr static bool value = std::is_convertible_v<SliceSpecifier, IndexType>;
+};
+
+template<class SliceSpecifier, class IndexType>
+constexpr bool is_index_slice_v = is_index_slice<SliceSpecifier, IndexType>::value;
+
+} // namespace detail
+
+//**********************************
+// layout_left submdspan_mapping
+//*********************************
+namespace detail {
+
+// Figure out whether to preserve layout_left
+template <class IndexType, size_t SubRank, class IndexSequence,
+          class... SliceSpecifiers>
+struct deduce_layout_left_submapping;
+
+template <class IndexType, size_t SubRank, size_t... Idx,
+          class... SliceSpecifiers>
+struct deduce_layout_left_submapping<
+    IndexType, SubRank, std::index_sequence<Idx...>, SliceSpecifiers...> {
+
+  using count_range = index_sequence_scan_impl<
+      0, (is_index_slice_v<SliceSpecifiers, IndexType> ? 0 : 1)...>;
+
+  constexpr static int gap_len =
+      (((Idx > 0 && count_range::get(Idx) == 1 &&
+         is_index_slice_v<SliceSpecifiers, IndexType>)
+            ? 1
+            : 0) +
+       ... + 0);
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr static bool layout_left_value() {
+    // Use layout_left for rank 0
+    if constexpr (SubRank == 0) {
+      return true;
+    // Use layout_left for rank 1 result if leftmost slice specifier is range like
+    } else if constexpr (SubRank == 1) {
+      return ((Idx > 0 || is_range_slice_v<SliceSpecifiers, IndexType>)&&...);
+    } else {
+      // Preserve if leftmost SubRank-1 slices are full_extent_t and
+      // the slice at idx Subrank - 1 is a range and
+      // for idx > SubRank the slice is an index
+      return ((((Idx <  SubRank - 1) && std::is_same_v<SliceSpecifiers, full_extent_t>) ||
+               ((Idx == SubRank - 1) && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+               ((Idx >  SubRank - 1) && is_index_slice_v<SliceSpecifiers, IndexType>)) && ...);
+    }
+#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__)
+    __builtin_unreachable();
+#endif
+  }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr static bool layout_left_padded_value() {
+    // Technically could also keep layout_left_padded for SubRank==0
+    // and SubRank==1 with leftmost slice specifier being a contiguous range
+    // but we intercept these cases separately
+
+    // In all other cases:
+    // leftmost slice must be range
+    // then there can be a gap with index slices
+    // then SubRank - 2 full_extent slices
+    // then another range slice
+    // then more index slices
+    // e.g. R I I I F F F R I I for obtaining a rank-5 from a rank-10
+    return ((((Idx == 0)                                       && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx > 0 && Idx <= gap_len)                     && is_index_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx > gap_len && Idx < gap_len + SubRank - 1) && std::is_same_v<SliceSpecifiers, full_extent_t>) || 
+             ((Idx == gap_len + SubRank - 1)                  && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx >  gap_len + SubRank - 1)                  && is_index_slice_v<SliceSpecifiers, IndexType>)) && ... );
+  }
+};
+
+} // namespace detail
+
+// Actual submdspan mapping call
+template <class Extents>
+template <class... SliceSpecifiers>
+MDSPAN_INLINE_FUNCTION constexpr auto
+layout_left::mapping<Extents>::submdspan_mapping_impl(
+    SliceSpecifiers... slices) const {
+
+  // compute sub extents
+  using src_ext_t = Extents;
+  auto dst_ext = submdspan_extents(extents(), slices...);
+  using dst_ext_t = decltype(dst_ext);
+
+  // figure out sub layout type
+  using deduce_layout = detail::deduce_layout_left_submapping<
+      typename dst_ext_t::index_type, dst_ext_t::rank(),
+      std::make_index_sequence<src_ext_t::rank()>,
+      SliceSpecifiers...>;
+
+  using dst_layout_t = std::conditional_t<
+      deduce_layout::layout_left_value(), layout_left,
+      std::conditional_t<
+          deduce_layout::layout_left_padded_value(),
+          MDSPAN_IMPL_PROPOSED_NAMESPACE::layout_left_padded<dynamic_extent>,
+          layout_stride>>;
+  using dst_mapping_t = typename dst_layout_t::template mapping<dst_ext_t>;
+
+  // Figure out if any slice's lower bound equals the corresponding extent.
+  // If so, bypass evaluating the layout mapping.  This fixes LWG Issue 4060.
+  const bool out_of_bounds =
+      detail::any_slice_out_of_bounds(this->extents(), slices...);
+  auto offset = static_cast<size_t>(
+      out_of_bounds ? this->required_span_size()
+                    : this->operator()(detail::first_of(slices)...));
+
+  if constexpr (std::is_same_v<dst_layout_t, layout_left>) {
+    // layout_left case
+    return submdspan_mapping_result<dst_mapping_t>{dst_mapping_t(dst_ext),
+                                                   offset};
+  } else if constexpr (std::is_same_v<dst_layout_t,
+                                      MDSPAN_IMPL_PROPOSED_NAMESPACE::
+                                          layout_left_padded<dynamic_extent>>) {
+    return submdspan_mapping_result<dst_mapping_t>{
+        dst_mapping_t(dst_ext, stride(1 + deduce_layout::gap_len)), offset};
+  } else {
+    // layout_stride case
+    auto inv_map = detail::inv_map_rank(std::integral_constant<size_t, 0>(),
+                                        std::index_sequence<>(), slices...);
+    return submdspan_mapping_result<dst_mapping_t> {
+      dst_mapping_t(dst_ext,
+                    detail::construct_sub_strides(
+                        *this, inv_map,
+// HIP needs deduction guides to have markups so we need to be explicit
+// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have
+// the issue But Clang-CUDA also doesn't accept the use of deduction guide so
+// disable it for CUDA altogether
+#if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA)
+                        std::tuple<decltype(detail::stride_of(slices))...>{
+                            detail::stride_of(slices)...})),
+#else
+                        std::tuple{detail::stride_of(slices)...})),
+#endif
+          offset
+    };
+  }
+#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__)
+  __builtin_unreachable();
+#endif
+}
+
+//**********************************
+// layout_right submdspan_mapping
+//*********************************
+namespace detail {
+
+// Figure out whether to preserve layout_right
+template <class IndexType, size_t SubRank, class IndexSequence,
+          class... SliceSpecifiers>
+struct deduce_layout_right_submapping;
+
+template <class IndexType, size_t SubRank, size_t... Idx,
+          class... SliceSpecifiers>
+struct deduce_layout_right_submapping<
+    IndexType, SubRank, std::index_sequence<Idx...>, SliceSpecifiers...> {
+
+  static constexpr size_t Rank = sizeof...(Idx);
+  using count_range = index_sequence_scan_impl<
+      0, (std::is_convertible_v<SliceSpecifiers, IndexType> ? 0 : 1)...>;
+  //__static_partial_sums<!std::is_convertible_v<SliceSpecifiers,
+  // IndexType>...>;
+  constexpr static int gap_len =
+      (((Idx < Rank - 1 && count_range::get(Idx) == SubRank - 1 &&
+         std::is_convertible_v<SliceSpecifiers, IndexType>)
+            ? 1
+            : 0) +
+       ... + 0);
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr static bool layout_right_value() {
+    // Use layout_right for rank 0
+    if constexpr (SubRank == 0) {
+      return true;
+    // Use layout_right for rank 1 result if rightmost slice specifier is range like
+    } else if constexpr (SubRank == 1) {
+      return ((Idx < Rank - 1 || is_range_slice_v<SliceSpecifiers, IndexType>)&&...);
+    } else {
+      // Preserve if rightmost SubRank-1 slices are full_extent_t and
+      // the slice at idx Rank-Subrank is a range and
+      // for idx < Rank - SubRank the slice is an index
+      return ((((Idx >= Rank - SubRank) && std::is_same_v<SliceSpecifiers, full_extent_t>) ||
+               ((Idx == Rank - SubRank) && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+               ((Idx <  Rank - SubRank) && is_index_slice_v<SliceSpecifiers, IndexType>)) && ...);
+    }
+#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__)
+    __builtin_unreachable();
+#endif
+  }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr static bool layout_right_padded_value() {
+    // Technically could also keep layout_right_padded for SubRank==0
+    // and SubRank==1 with rightmost slice specifier being a contiguous range
+    // but we intercept these cases separately
+
+    // In all other cases:
+    // rightmost slice must be range
+    // then there can be a gap with index slices
+    // then SubRank - 2 full_extent slices
+    // then another range slice
+    // then more index slices
+    // e.g. I I R F F F I I I R for obtaining a rank-5 from a rank-10
+    return ((((Idx == Rank - 1)                                               && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx >= Rank - gap_len - 1 && Idx < Rank - 1)                  && is_index_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx >  Rank - gap_len - SubRank && Idx < Rank - gap_len - 1) && std::is_same_v<SliceSpecifiers, full_extent_t>) ||
+             ((Idx == Rank - gap_len - SubRank)                              && is_range_slice_v<SliceSpecifiers, IndexType>) ||
+             ((Idx <  Rank - gap_len - SubRank)                              && is_index_slice_v<SliceSpecifiers, IndexType>)) && ... );
+  }
+};
+
+} // namespace detail
+
+// Actual submdspan mapping call
+template <class Extents>
+template <class... SliceSpecifiers>
+MDSPAN_INLINE_FUNCTION constexpr auto
+layout_right::mapping<Extents>::submdspan_mapping_impl(
+    SliceSpecifiers... slices) const {
+
+  // compute sub extents
+  using src_ext_t = Extents;
+  auto dst_ext = submdspan_extents(extents(), slices...);
+  using dst_ext_t = decltype(dst_ext);
+
+  // figure out sub layout type
+  using deduce_layout = detail::deduce_layout_right_submapping<
+      typename dst_ext_t::index_type, dst_ext_t::rank(),
+      std::make_index_sequence<src_ext_t::rank()>,
+      SliceSpecifiers...>;
+
+  using dst_layout_t = std::conditional_t<
+      deduce_layout::layout_right_value(), layout_right,
+      std::conditional_t<
+          deduce_layout::layout_right_padded_value(),
+          MDSPAN_IMPL_PROPOSED_NAMESPACE::layout_right_padded<dynamic_extent>,
+          layout_stride>>;
+  using dst_mapping_t = typename dst_layout_t::template mapping<dst_ext_t>;
+
+  // Figure out if any slice's lower bound equals the corresponding extent.
+  // If so, bypass evaluating the layout mapping.  This fixes LWG Issue 4060.
+  const bool out_of_bounds =
+      detail::any_slice_out_of_bounds(this->extents(), slices...);
+  auto offset = static_cast<size_t>(
+      out_of_bounds ? this->required_span_size()
+                    : this->operator()(detail::first_of(slices)...));
+
+  if constexpr (std::is_same_v<dst_layout_t, layout_right>) {
+    // layout_right case
+    return submdspan_mapping_result<dst_mapping_t>{dst_mapping_t(dst_ext),
+                                                   offset};
+  } else if constexpr (std::is_same_v<
+                           dst_layout_t,
+                           MDSPAN_IMPL_PROPOSED_NAMESPACE::layout_right_padded<
+                               dynamic_extent>>) {
+    return submdspan_mapping_result<dst_mapping_t>{
+        dst_mapping_t(dst_ext,
+                      stride(src_ext_t::rank() - 2 - deduce_layout::gap_len)),
+        offset};
+  } else {
+    // layout_stride case
+    auto inv_map = detail::inv_map_rank(std::integral_constant<size_t, 0>(),
+                                        std::index_sequence<>(), slices...);
+    return submdspan_mapping_result<dst_mapping_t> {
+      dst_mapping_t(dst_ext,
+                    detail::construct_sub_strides(
+                        *this, inv_map,
+// HIP needs deduction guides to have markups so we need to be explicit
+// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have
+// the issue But Clang-CUDA also doesn't accept the use of deduction guide so
+// disable it for CUDA altogether
+#if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA)
+                        std::tuple<decltype(detail::stride_of(slices))...>{
+                            detail::stride_of(slices)...})),
+#else
+                        std::tuple{detail::stride_of(slices)...})),
+#endif
+          offset
+    };
+  }
+#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__)
+  __builtin_unreachable();
+#endif
+}
+
+//**********************************
+// layout_stride submdspan_mapping
+//*********************************
+template <class Extents>
+template <class... SliceSpecifiers>
+MDSPAN_INLINE_FUNCTION constexpr auto
+layout_stride::mapping<Extents>::submdspan_mapping_impl(
+    SliceSpecifiers... slices) const {
+  auto dst_ext = submdspan_extents(extents(), slices...);
+  using dst_ext_t = decltype(dst_ext);
+  auto inv_map = detail::inv_map_rank(std::integral_constant<size_t, 0>(),
+                                      std::index_sequence<>(), slices...);
+  using dst_mapping_t = typename layout_stride::template mapping<dst_ext_t>;
+
+  // Figure out if any slice's lower bound equals the corresponding extent.
+  // If so, bypass evaluating the layout mapping.  This fixes LWG Issue 4060.
+  const bool out_of_bounds =
+      detail::any_slice_out_of_bounds(this->extents(), slices...);
+  auto offset = static_cast<size_t>(
+      out_of_bounds ? this->required_span_size()
+                    : this->operator()(detail::first_of(slices)...));
+
+  return submdspan_mapping_result<dst_mapping_t> {
+    dst_mapping_t(dst_ext,
+                  detail::construct_sub_strides(
+                      *this, inv_map,
+// HIP needs deduction guides to have markups so we need to be explicit
+// NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have
+// the issue
+#if defined(_MDSPAN_HAS_HIP) ||                                                \
+    (defined(__NVCC__) &&                                                      \
+     (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120)
+                      std::tuple<decltype(detail::stride_of(slices))...>(
+                          detail::stride_of(slices)...))),
+#else
+                      std::tuple(detail::stride_of(slices)...))),
+#endif
+        offset
+  };
+}
+
+} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE
+
+#if defined __NVCC__
+#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__
+#pragma nv_diagnostic pop
+#else
+#ifdef __CUDA_ARCH__
+#pragma diagnostic pop
+#endif
+#endif
+#elif defined __NVCOMPILER
+#pragma diagnostic pop
+#endif
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded.hpp b/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..99e24fa450ae7973c3ad44e314bbefbc5d82185a
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded.hpp
@@ -0,0 +1,852 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+//
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include <cassert>
+#include "layout_padded_fwd.hpp"
+#include "../__p0009_bits/dynamic_extent.hpp"
+#include "../__p0009_bits/extents.hpp"
+#include "../__p0009_bits/mdspan.hpp"
+#include "../__p0009_bits/layout_left.hpp"
+#include "../__p0009_bits/layout_right.hpp"
+#include "../__p0009_bits/layout_stride.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace MDSPAN_IMPL_PROPOSED_NAMESPACE {
+
+namespace detail {
+template<class _T>
+MDSPAN_INLINE_FUNCTION
+constexpr _T
+find_next_multiple(_T alignment, _T offset)
+{
+  if ( alignment == 0 ) {
+    return _T(0);
+  } else {
+    return ( ( offset + alignment - 1 ) / alignment) * alignment;
+  }
+}
+
+template <class _ExtentsType, size_t _PaddingValue, size_t _ExtentToPadIdx>
+MDSPAN_INLINE_FUNCTION constexpr size_t get_actual_static_padding_value() {
+  constexpr auto rank = _ExtentsType::rank();
+
+  if constexpr (rank <= typename _ExtentsType::rank_type(1)) {
+    return 0;
+  } else if constexpr (_PaddingValue != dynamic_extent &&
+                       _ExtentsType::static_extent(_ExtentToPadIdx) !=
+                           dynamic_extent) {
+    static_assert(
+        (_PaddingValue != 0) ||
+            (_ExtentsType::static_extent(_ExtentToPadIdx) == 0),
+        "padding stride can be 0 only if "
+        "extents_type::static_extent(extent-to-pad) is 0 or dynamic_extent");
+    return find_next_multiple(_PaddingValue,
+                                _ExtentsType::static_extent(_ExtentToPadIdx));
+  } else {
+    return dynamic_extent;
+  }
+  // Missing return statement warning from NVCC
+#ifdef __NVCC__
+  return 0;
+#endif
+}
+
+template <size_t _PaddingValue, typename _Extents, size_t _ExtentToPadIdx, size_t _Rank, typename Enabled = void>
+struct static_array_type_for_padded_extent
+{
+  static constexpr size_t padding_value = _PaddingValue;
+  using index_type = typename _Extents::index_type;
+  using extents_type = _Extents;
+  using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array<
+      index_type, size_t, dynamic_extent,
+      detail::get_actual_static_padding_value<extents_type, padding_value,
+                                                _ExtentToPadIdx>()>;
+};
+
+template <size_t _PaddingValue, typename _Extents, size_t _ExtentToPadIdx, size_t Rank>
+struct static_array_type_for_padded_extent<_PaddingValue, _Extents,
+                                             _ExtentToPadIdx, Rank, std::enable_if_t<Rank <= 1>> {
+  using index_type = typename _Extents::index_type;
+  using extents_type = _Extents;
+  using type =
+      ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array<
+          index_type, size_t, dynamic_extent, 0>;
+};
+
+template <size_t _PaddingValue, typename _Extents, size_t _ExtentToPadIdx>
+struct padded_extent {
+  static constexpr size_t padding_value = _PaddingValue;
+  using index_type = typename _Extents::index_type;
+  using extents_type = _Extents;
+  using static_array_type = typename static_array_type_for_padded_extent<
+      padding_value, _Extents, _ExtentToPadIdx, _Extents::rank()>::type;
+
+  static constexpr auto static_value() { return static_array_type::static_value(0); }
+
+  MDSPAN_INLINE_FUNCTION
+  static constexpr static_array_type
+  init_padding(const _Extents &exts) {
+    if constexpr ((_Extents::rank() > 1) && (padding_value == dynamic_extent)) {
+      return {exts.extent(_ExtentToPadIdx)};
+    } else {
+      return init_padding(exts, padding_value);
+    }
+  // Missing return statement warning from NVCC
+#ifdef __NVCC__
+  return {};
+#endif
+  }
+
+  MDSPAN_INLINE_FUNCTION static constexpr static_array_type
+  init_padding([[maybe_unused]] const _Extents &exts,
+               [[maybe_unused]] index_type pv) {
+    if constexpr (_Extents::rank() > 1) {
+      return {find_next_multiple(pv,
+                                   exts.extent(_ExtentToPadIdx))};
+    } else {
+      return {};
+    }
+  // Missing return statement warning from NVCC
+#ifdef __NVCC__
+  return {};
+#endif
+  }
+
+  template <typename _Mapping, size_t _PaddingStrideIdx>
+  MDSPAN_INLINE_FUNCTION static constexpr static_array_type
+  init_padding([[maybe_unused]] const _Mapping &other_mapping,
+                      std::integral_constant<size_t, _PaddingStrideIdx>) {
+    if constexpr (_Extents::rank() > 1) {
+      return {other_mapping.stride(_PaddingStrideIdx)};
+    } else {
+      return {};
+    }
+  // Missing return statement warning from NVCC
+#ifdef __NVCC__
+  return {};
+#endif
+  }
+};
+} // namespace detail
+
+template <size_t PaddingValue>
+template <class Extents>
+class layout_left_padded<PaddingValue>::mapping {
+public:
+  static constexpr size_t padding_value = PaddingValue;
+
+  using extents_type = Extents;
+  using index_type = typename extents_type::index_type;
+  using size_type = typename extents_type::size_type;
+  using rank_type = typename extents_type::rank_type;
+  using layout_type = layout_left_padded<padding_value>;
+
+#ifndef MDSPAN_INTERNAL_TEST
+private:
+#endif // MDSPAN_INTERNAL_TEST
+
+  static constexpr rank_type padded_stride_idx = detail::layout_padded_constants<layout_type, extents_type>::padded_stride_idx;
+  static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants<layout_type, extents_type>::extent_to_pad_idx;
+
+  static_assert((padding_value != 0)
+                || (extents_type::static_extent(extent_to_pad_idx) == 0)
+                || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent),
+                "out of bounds access for rank 0");
+
+  using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >;
+
+  static constexpr size_t static_padding_stride = padded_stride_type::static_value();
+
+  typename padded_stride_type::static_array_type padded_stride = {};
+  extents_type exts = {};
+
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<>) const {
+    return 0;
+  }
+
+  template <size_t Rank, class IndexOffset>
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<Rank>, IndexOffset index_offset) const {
+    return index_offset;
+  }
+
+  template <size_t... Ranks, class... IndexOffsets>
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<Ranks...>,
+                 IndexOffsets... index_offsets) const {
+    index_type indices[] = {static_cast<index_type>(index_offsets)...};
+    // self-recursive fold trick from
+    // https://github.com/llvm/llvm-project/blob/96e1914aa2e6d8966acbfbe2f4d184201f1aa318/libcxx/include/mdspan/layout_left.h#L144
+    index_type res = 0;
+    ((res = indices[extents_type::rank() - 1 - Ranks] +
+            ((extents_type::rank() - 1 - Ranks) == extent_to_pad_idx
+                 ? padded_stride.value(0)
+                 : exts.extent(extents_type::rank() - 1 - Ranks)) *
+                res),
+     ...);
+    return res;
+  }
+
+public:
+#if !MDSPAN_HAS_CXX_20
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr mapping()
+      : mapping(extents_type{})
+  {}
+#else
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+  constexpr mapping()
+    requires(static_padding_stride != dynamic_extent) = default;
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping()
+    requires(static_padding_stride == dynamic_extent)
+      : mapping(extents_type{})
+  {}
+#endif
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default;
+
+  /**
+   * Initializes the mapping with the given extents.
+   *
+   * \param ext the given extents
+   */
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const extents_type& ext)
+    : padded_stride(padded_stride_type::init_padding(ext)), exts(ext)
+  {}
+
+  /**
+   * Initializes the mapping with the given extents and the specified padding value.
+   *
+   * This overload participates in overload resolution only if `is_convertible_v<Size, index_type>`
+   * is `true` and `is_nothrow_constructible_v<index_type, Size>` is `true`
+   *
+   * \param ext the given extents
+   * \param padding_value the padding value
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+    class _Size,
+    /* requires */ (
+      std::is_convertible_v<_Size, index_type>
+      && std::is_nothrow_constructible_v<index_type, _Size>
+    )
+  )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const extents_type &ext, _Size dynamic_padding_value)
+      : padded_stride(padded_stride_type::init_padding(ext, dynamic_padding_value)), exts(ext)
+  {
+    assert((padding_value == dynamic_extent) || (static_cast<index_type>(padding_value) == static_cast<index_type>(dynamic_padding_value)));
+  }
+
+  /**
+   * Converting constructor from `layout_left::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `is_constructible_v<extents_type, OtherExtents>` is true. If
+   * `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`,
+   * or `OtherExtents::static_extent(0)` must be `dynamic_extent`; otherwise,
+   * `OtherExtents::static_extent(0)` must be equal to the least multiple of
+   * `padding_value` greater than or equal to `extents_type::static_extent(0)`
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _OtherExtents,
+      /* requires */ (std::is_constructible_v<extents_type, _OtherExtents>))
+  MDSPAN_CONDITIONAL_EXPLICIT(
+      (!std::is_convertible_v<_OtherExtents, extents_type>))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const layout_left::mapping<_OtherExtents> &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {
+    static_assert(
+        (_OtherExtents::rank() > 1) ||
+        (static_padding_stride != dynamic_extent) ||
+        (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) ||
+        (static_padding_stride ==
+         _OtherExtents::static_extent(extent_to_pad_idx)));
+  }
+
+  /**
+   * Converting constructor from `layout_stride::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `is_constructible_v<extents_type, OtherExtents>` is true
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _OtherExtents,
+      /* requires */ (std::is_constructible_v<extents_type, _OtherExtents>))
+  MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {}
+
+  /**
+   * Converting constructor from `layout_left_padded::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `is_constructible_v<extents_type, OtherExtents>` is true. Either
+   * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or
+   * `padding_value == OtherPaddingStride`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value
+                          &&std::is_constructible_v<
+                              extents_type, typename _Mapping::extents_type>))
+  MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 &&
+                               (padding_value == dynamic_extent ||
+                                _Mapping::padding_value == dynamic_extent)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const _Mapping &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {
+    static_assert(padding_value == dynamic_extent ||
+                  _Mapping::padding_value == dynamic_extent ||
+                  padding_value == _Mapping::padding_value);
+  }
+
+  /**
+   * Converting constructor from `layout_right_padded::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `extents_type::rank()` is 0 or 1 and `is_constructible_v<extents_type,
+   * OtherExtents>` is `true`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value
+                              &&extents_type::rank() <= 1 &&
+                      std::is_constructible_v<extents_type,
+                                              typename _Mapping::extents_type>))
+  MDSPAN_CONDITIONAL_EXPLICIT(
+      (!std::is_convertible_v<typename _Mapping::extents_type, extents_type>))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const _Mapping &other_mapping) noexcept
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping.extents(),
+            other_mapping.extents().extent(extent_to_pad_idx))),
+        exts(other_mapping.extents()) {}
+
+  MDSPAN_INLINE_FUNCTION constexpr const extents_type &
+  extents() const noexcept {
+    return exts;
+  }
+
+  MDSPAN_INLINE_FUNCTION constexpr std::array<index_type, extents_type::rank()>
+  strides() const noexcept {
+    if constexpr (extents_type::rank() == 0) {
+      return {};
+    } else if constexpr (extents_type::rank() == 1) {
+      return {1};
+    } else {
+      index_type value = 1;
+      std::array<index_type, extents_type::rank()> s{};
+      s[extent_to_pad_idx] = value;
+      value *= padded_stride.value(0);
+      for (rank_type r = extent_to_pad_idx + 1; r < extents_type::rank() - 1;
+           ++r) {
+        s[r] = value;
+        value *= exts.extent(r);
+      }
+      s[extents_type::rank() - 1] = value;
+      return s;
+    }
+  }
+
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  required_span_size() const noexcept {
+    if constexpr (extents_type::rank() == 0) {
+      return 1;
+    } else if constexpr (extents_type::rank() == 1) {
+      return exts.extent(0);
+    } else {
+      index_type value = padded_stride.value(0);
+      for (rank_type r = 1; r < extents_type::rank(); ++r) {
+        value *= exts.extent(r);
+      }
+      return value;
+    }
+  }
+
+  /**
+   * Return the mapping given the provided indices per rank.
+   *
+   * This overload participates in overload resolution only if:
+   * - `sizeof...(Indices) == extents_type::rank()`,
+   * - `(is_convertible_v<Indices, index_type> && ...) is true`, and
+   * - (is_nothrow_constructible_v<index_type, Indices> && ...) is true.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class... _Indices,
+      /* requires */ (sizeof...(_Indices) == extents_type::rank() &&
+                      (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::
+                           are_valid_indices<index_type, _Indices...>())))
+  MDSPAN_INLINE_FUNCTION constexpr size_t
+  operator()(_Indices... idxs) const noexcept {
+#if !defined(NDEBUG)
+    ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_all_indices(this->extents(),
+                                                                idxs...);
+#endif // ! NDEBUG
+    return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...);
+  }
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept {
+    return true;
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept {
+    return (extents_type::rank() <= rank_type(1)) ||
+           (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent &&
+            extents_type::static_extent(extent_to_pad_idx) ==
+                padded_stride_type::static_value());
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept {
+    return true;
+  }
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept {
+    return true;
+  }
+  MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept {
+    return (extents_type::rank() < 2) ||
+           (exts.extent(extent_to_pad_idx) == padded_stride.value(0));
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept {
+    return true;
+  }
+
+  MDSPAN_INLINE_FUNCTION
+  constexpr index_type stride(rank_type r) const noexcept {
+    assert(r < extents_type::rank());
+    if (r == 0)
+      return index_type(1);
+
+    index_type value = padded_stride.value(0);
+    for (rank_type k = 1; k < r; k++)
+      value *= exts.extent(k);
+
+    return value;
+  }
+
+  /**
+   * Equality operator between `layout_left_padded`s
+   *
+   * This overload only participates in overload resolution if
+   * `OtherExtents::rank() == extents_type::rank()`.
+   *
+   * \note There is currently a difference from p2642r2, where this function is
+   * specified as taking `layout_left_padded< padding_value >::mapping<
+   * Extents>`. However, this makes `padding_value` non-deducible.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value &&
+                      (_Mapping::extents_type::rank() == extents_type::rank())))
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator==(const mapping &left, const _Mapping &right) noexcept {
+    // Workaround for some compilers not short-circuiting properly with
+    // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a
+    // rank 0 mapping
+    bool strides_equal = true;
+    if constexpr (extents_type::rank() > rank_type(1)) {
+      strides_equal =
+          left.stride(padded_stride_idx) == right.stride(padded_stride_idx);
+    }
+    return (left.extents() == right.extents()) && strides_equal;
+  }
+
+#if !MDSPAN_HAS_CXX_20
+  /**
+   * Inequality operator between `layout_left_padded`s
+   *
+   * This overload only participates in overload resolution if
+   * `OtherExtents::rank() == extents_type::rank()`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value &&
+                      (_Mapping::extents_type::rank() == extents_type::rank())))
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator!=(const mapping &left, const _Mapping &right) noexcept {
+    return !(left == right);
+  }
+#endif
+
+   // [mdspan.submdspan.mapping], submdspan mapping specialization
+   template<class... SliceSpecifiers>
+     constexpr auto submdspan_mapping_impl(
+       SliceSpecifiers... slices) const;
+
+   template<class... SliceSpecifiers>
+     friend constexpr auto submdspan_mapping(
+       const mapping& src, SliceSpecifiers... slices) {
+         return src.submdspan_mapping_impl(slices...);
+     }
+};
+
+template <size_t PaddingValue>
+template <class Extents>
+class layout_right_padded<PaddingValue>::mapping {
+public:
+  static constexpr size_t padding_value = PaddingValue;
+
+  using extents_type = Extents;
+  using index_type = typename extents_type::index_type;
+  using size_type = typename extents_type::size_type;
+  using rank_type = typename extents_type::rank_type;
+  using layout_type = layout_right_padded<padding_value>;
+
+#ifndef MDSPAN_INTERNAL_TEST
+  private:
+#endif // MDSPAN_INTERNAL_TEST
+
+  static constexpr rank_type padded_stride_idx = detail::layout_padded_constants<layout_type, extents_type>::padded_stride_idx;
+  static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants<layout_type, extents_type>::extent_to_pad_idx;
+
+  static_assert((padding_value != 0)
+                || (extents_type::static_extent(extent_to_pad_idx) == 0)
+                || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent),
+                "if padding stride is 0, static_extent(extent-to-pad-rank) must also be 0 or dynamic_extent");
+
+  using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >;
+  static constexpr size_t static_padding_stride = padded_stride_type::static_value();
+
+  typename padded_stride_type::static_array_type padded_stride = {};
+  extents_type exts = {};
+
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<>) const {
+    return 0;
+  }
+
+  template <size_t Rank, class IndexOffset>
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<Rank>, IndexOffset index_offset) const {
+    return index_offset;
+  }
+
+  template <size_t... Ranks, class... IndexOffsets>
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  compute_offset(std::index_sequence<Ranks...>,
+                 IndexOffsets... index_offsets) const {
+    // self-recursive fold trick from
+    // https://github.com/llvm/llvm-project/blob/4d9771741d40cc9cfcccb6b033f43689d36b705a/libcxx/include/mdspan/layout_right.h#L141
+    index_type res = 0;
+    ((res = static_cast<index_type>(index_offsets) +
+            (Ranks == extent_to_pad_idx ? padded_stride.value(0)
+                                        : exts.extent(Ranks)) *
+                res),
+     ...);
+    return res;
+  }
+
+public:
+#if !MDSPAN_HAS_CXX_20
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+      constexpr mapping()
+      : mapping(extents_type{})
+  {}
+#else
+  MDSPAN_INLINE_FUNCTION_DEFAULTED
+      constexpr mapping()
+    requires(static_padding_stride != dynamic_extent) = default;
+
+  MDSPAN_INLINE_FUNCTION
+      constexpr mapping()
+    requires(static_padding_stride == dynamic_extent)
+      : mapping(extents_type{})
+  {}
+#endif
+
+  MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default;
+  MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default;
+
+  /**
+   * Initializes the mapping with the given extents.
+   *
+   * \param ext the given extents
+   */
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const extents_type &ext)
+      : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) {}
+
+  /**
+   * Initializes the mapping with the given extents and the specified padding value.
+   *
+   * This overload participates in overload resolution only if `is_convertible_v<Size, index_type>`
+   * is `true` and `is_nothrow_constructible_v<index_type, Size>` is `true`
+   *
+   * \param ext the given extents
+   * \param padding_value the padding value
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Size,
+      /* requires */ (
+          std::is_convertible_v<_Size, index_type>
+              && std::is_nothrow_constructible_v<index_type, _Size>
+          )
+      )
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const extents_type &ext, _Size dynamic_padding_value)
+      : padded_stride(padded_stride_type::init_padding(ext, static_cast<index_type>(dynamic_padding_value))),
+        exts(ext) {
+    assert((padding_value == dynamic_extent) ||
+           (static_cast<index_type>(padding_value) == static_cast<index_type>(dynamic_padding_value)));
+  }
+
+  /**
+   * Converting constructor from `layout_right::mapping`.
+   *
+   * This overload participates in overload resolution only if `is_constructible_v<extents_type, OtherExtents>` is true.
+   * If `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, or `OtherExtents::static_extent(0)` must be `dynamic_extent`;
+   * otherwise, `OtherExtents::static_extent(0)` must be equal to the least multiple of `padding_value` greater than or equal to `extents_type::static_extent(0)`
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _OtherExtents,
+      /* requires */ (std::is_constructible_v<extents_type, _OtherExtents>))
+  MDSPAN_CONDITIONAL_EXPLICIT(
+      (!std::is_convertible_v<_OtherExtents, extents_type>))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const layout_right::mapping<_OtherExtents> &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {
+    static_assert(
+        (_OtherExtents::rank() > 1) ||
+        (padded_stride_type::static_value() != dynamic_extent) ||
+        (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) ||
+        (padded_stride_type::static_value() ==
+         _OtherExtents::static_extent(extent_to_pad_idx)));
+  }
+
+  /**
+   * Converting constructor from `layout_stride::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `is_constructible_v<extents_type, OtherExtents>` is true
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _OtherExtents,
+      /* requires */ (std::is_constructible_v<extents_type, _OtherExtents>))
+  MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {}
+
+  /**
+   * Converting constructor from `layout_right_padded::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `is_constructible_v<extents_type, OtherExtents>` is true. Either
+   * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or
+   * `padding_value == OtherPaddingStride`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value
+                          &&std::is_constructible_v<
+                              extents_type, typename _Mapping::extents_type>))
+  MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 &&
+                               (padding_value == dynamic_extent ||
+                                _Mapping::padding_value == dynamic_extent)))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const _Mapping &other_mapping)
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping,
+            std::integral_constant<size_t, padded_stride_idx>{})),
+        exts(other_mapping.extents()) {
+    static_assert(padding_value == dynamic_extent ||
+                  _Mapping::padding_value == dynamic_extent ||
+                  padding_value == _Mapping::padding_value);
+  }
+
+  /**
+   * Converting constructor from `layout_left_padded::mapping`.
+   *
+   * This overload participates in overload resolution only if
+   * `extents_type::rank()` is 0 or 1 and `is_constructible_v<extents_type,
+   * OtherExtents>` is `true`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value
+                              &&extents_type::rank() <= 1 &&
+                      std::is_constructible_v<extents_type,
+                                              typename _Mapping::extents_type>))
+  MDSPAN_CONDITIONAL_EXPLICIT(
+      (!std::is_convertible_v<typename _Mapping::extents_type, extents_type>))
+  MDSPAN_INLINE_FUNCTION
+  constexpr mapping(const _Mapping &other_mapping) noexcept
+      : padded_stride(padded_stride_type::init_padding(
+            other_mapping.extents(),
+            other_mapping.extents().extent(extent_to_pad_idx))),
+        exts(other_mapping.extents()) {}
+
+  MDSPAN_INLINE_FUNCTION constexpr const extents_type &
+  extents() const noexcept {
+    return exts;
+  }
+
+  MDSPAN_INLINE_FUNCTION constexpr std::array<index_type, extents_type::rank()>
+  strides() const noexcept {
+    if constexpr (extents_type::rank() == 0) {
+      return {};
+    } else if constexpr (extents_type::rank() == 1) {
+      return {1};
+    } else {
+      index_type value = 1;
+      std::array<index_type, extents_type::rank()> s{};
+      s[extent_to_pad_idx] = value;
+      value *= padded_stride.value(0);
+      for (rank_type r = extent_to_pad_idx - 1; r > 0; --r) {
+        s[r] = value;
+        value *= exts.extent(r);
+      }
+      s[0] = value;
+      return s;
+    }
+  }
+
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  required_span_size() const noexcept {
+    if constexpr (extents_type::rank() == 0) {
+      return 1;
+    } else if constexpr (extents_type::rank() == 1) {
+      return exts.extent(0);
+    } else {
+      index_type value = 1;
+      for (rank_type r = 0; r < extent_to_pad_idx; ++r) {
+        value *= exts.extent(r);
+      }
+      return value * padded_stride.value(0);
+    }
+  }
+
+  /**
+   * Return the mapping given the provided indices per rank.
+   *
+   * This overload participates in overload resolution only if:
+   * - `sizeof...(Indices) == extents_type::rank()`,
+   * - `(is_convertible_v<Indices, index_type> && ...) is true`, and
+   * - (is_nothrow_constructible_v<index_type, Indices> && ...) is true.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class... _Indices,
+      /* requires */ (sizeof...(_Indices) == extents_type::rank() &&
+                      (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::
+                           are_valid_indices<index_type, _Indices...>())))
+  MDSPAN_INLINE_FUNCTION constexpr size_t
+  operator()(_Indices... idxs) const noexcept {
+    return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...);
+  }
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept {
+    return true;
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept {
+    return (extents_type::rank() <= rank_type(1)) ||
+           (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent &&
+            extents_type::static_extent(extent_to_pad_idx) ==
+                padded_stride_type::static_value());
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept {
+    return true;
+  }
+
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept {
+    return true;
+  }
+  MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept {
+    return (extents_type::rank() < 2) ||
+           (exts.extent(extent_to_pad_idx) == padded_stride.value(0));
+  }
+  MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept {
+    return true;
+  }
+
+  MDSPAN_INLINE_FUNCTION constexpr index_type
+  stride(rank_type r) const noexcept {
+    assert(r < extents_type::rank());
+    if (r == extents_type::rank() - 1)
+      return index_type(1);
+
+    index_type value = padded_stride.value(0);
+    for (rank_type k = extents_type::rank() - 2; k > r; k--)
+      value *= exts.extent(k);
+
+    return value;
+  }
+
+  /**
+   * Equality operator between `layout_right_padded`s
+   *
+   * This overload only participates in overload resolution if
+   * `OtherExtents::rank() == extents_type::rank()`.
+   *
+   * \note There is currently a difference from p2642r2, where this function is
+   * specified as taking `layout_right_padded< padding_value >::mapping<
+   * Extents>`. However, this makes `padding_value` non-deducible.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value &&
+                      (_Mapping::extents_type::rank() == extents_type::rank())))
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator==(const mapping &left, const _Mapping &right) noexcept {
+    // Workaround for some compilers not short-circuiting properly with
+    // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a
+    // rank 0 mapping
+    bool strides_equal = true;
+    if constexpr (extents_type::rank() > rank_type(1)) {
+      strides_equal =
+          left.stride(padded_stride_idx) == right.stride(padded_stride_idx);
+    }
+    return (left.extents() == right.extents()) && strides_equal;
+  }
+
+#if !MDSPAN_HAS_CXX_20
+  /**
+   * Inequality operator between `layout_right_padded`s
+   *
+   * This overload only participates in overload resolution if
+   * `OtherExtents::rank() == extents_type::rank()`.
+   */
+  MDSPAN_TEMPLATE_REQUIRES(
+      class _Mapping,
+      /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value &&
+                      (_Mapping::extents_type::rank() == extents_type::rank())))
+  MDSPAN_INLINE_FUNCTION friend constexpr bool
+  operator!=(const mapping &left, const _Mapping &right) noexcept {
+    return !(left == right);
+  }
+#endif
+};
+}
+}
diff --git a/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp b/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b5eaac952bc8998c9d9710d8c46004f3c5da6b10
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp
@@ -0,0 +1,131 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+//
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+#pragma once
+
+#include <cassert>
+#include "../__p0009_bits/dynamic_extent.hpp"
+#include "../__p0009_bits/utility.hpp"
+
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+namespace MDSPAN_IMPL_PROPOSED_NAMESPACE {
+
+template <size_t padding_value = dynamic_extent>
+struct layout_left_padded {
+  template <class _Extents>
+  class mapping;
+};
+
+template <size_t padding_value = dynamic_extent>
+struct layout_right_padded {
+  template <class _Extents>
+  class mapping;
+};
+
+namespace detail {
+// The layout_padded_constants structs are only useful if rank > 1, otherwise they may wrap
+template <class _Layout, class _ExtentsType>
+struct layout_padded_constants;
+
+template <class _ExtentsType, size_t _PaddingStride>
+struct layout_padded_constants<layout_left_padded<_PaddingStride>, _ExtentsType>
+{
+  using rank_type = typename _ExtentsType::rank_type;
+  static constexpr rank_type padded_stride_idx = 1;
+  static constexpr rank_type extent_to_pad_idx = 0;
+};
+
+template <class _ExtentsType, size_t _PaddingStride>
+struct layout_padded_constants<layout_right_padded<_PaddingStride>, _ExtentsType>
+{
+  using rank_type = typename _ExtentsType::rank_type;
+  static constexpr rank_type padded_stride_idx = _ExtentsType::rank() - 2;
+  static constexpr rank_type extent_to_pad_idx = _ExtentsType::rank() - 1;
+};
+
+template <class _Layout>
+struct is_layout_left_padded : std::false_type {};
+
+template <size_t _PaddingStride>
+struct is_layout_left_padded<layout_left_padded<_PaddingStride>> : std::true_type {};
+
+template <class _Mapping, class _Enabled = void>
+struct is_layout_left_padded_mapping : std::false_type {};
+
+template <class _Mapping>
+struct is_layout_left_padded_mapping<_Mapping,
+  std::enable_if_t<std::is_same<_Mapping, typename layout_left_padded<_Mapping::padding_value>::template mapping<typename _Mapping::extents_type>>::value>>
+    : std::true_type {};
+
+template <class _Layout>
+struct is_layout_right_padded : std::false_type {};
+
+template <size_t _PaddingStride>
+struct is_layout_right_padded<layout_right_padded<_PaddingStride>> : std::true_type {};
+
+template <class _Mapping, class _Enabled = void>
+struct is_layout_right_padded_mapping : std::false_type {};
+
+template <class _Mapping>
+struct is_layout_right_padded_mapping<_Mapping,
+  std::enable_if_t<std::is_same<_Mapping, typename layout_right_padded<_Mapping::padding_value>::template mapping<typename _Mapping::extents_type>>::value>>
+    : std::true_type {};
+
+
+template <class _LayoutExtentsType, class _PaddedLayoutMappingType>
+constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>) {}
+
+template <class _LayoutExtentsType, class _PaddedLayoutMappingType>
+constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>) {}
+
+template <class _LayoutExtentsType, class _PaddedLayoutMappingType, std::size_t N>
+constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<N>)
+{
+  using extents_type = typename _PaddedLayoutMappingType::extents_type;
+  constexpr auto padding_value = _PaddedLayoutMappingType::padding_value;
+  constexpr auto idx = layout_padded_constants<typename _PaddedLayoutMappingType::layout_type, _LayoutExtentsType >::extent_to_pad_idx;
+
+  constexpr auto statically_determinable =
+    (_LayoutExtentsType::static_extent(idx) != dynamic_extent) &&
+    (extents_type::static_extent(idx) != dynamic_extent) &&
+    (padding_value != dynamic_extent);
+
+  static_assert(not statically_determinable or
+                (padding_value == 0
+                 ? _LayoutExtentsType::static_extent(idx) == 0
+                 : _LayoutExtentsType::static_extent(idx) % padding_value == 0),
+                "");
+}
+
+template <typename _ExtentsType, typename _OtherMapping>
+constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>,
+                                                                        const _OtherMapping&) {}
+template <typename _ExtentsType, typename _OtherMapping>
+constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>,
+                                                                        const _OtherMapping&) {}
+template <typename _ExtentsType, typename _OtherMapping, std::size_t N>
+constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<N>,
+                                                                        const _OtherMapping &other_mapping) {
+  constexpr auto padded_stride_idx =
+    layout_padded_constants<typename _OtherMapping::layout_type,
+                            _ExtentsType>::padded_stride_idx;
+  constexpr auto extent_to_pad_idx = layout_padded_constants<typename _OtherMapping::layout_type, _ExtentsType>::extent_to_pad_idx;
+  MDSPAN_IMPL_PRECONDITION(other_mapping.stride(padded_stride_idx) == other_mapping.extents().extent(extent_to_pad_idx));
+}
+
+
+}
+}
+}
diff --git a/tests/integration/deps/mdspan/include/experimental/mdarray b/tests/integration/deps/mdspan/include/experimental/mdarray
new file mode 100644
index 0000000000000000000000000000000000000000..642d1f5ad9e704ade699bbe89d97344c34e6d5a3
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/mdarray
@@ -0,0 +1,28 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE
+  #define MDSPAN_IMPL_STANDARD_NAMESPACE std
+#endif
+
+#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE
+  #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental
+#endif
+
+#include "mdspan"
+#include "../mdspan/mdarray.hpp"
diff --git a/tests/integration/deps/mdspan/include/experimental/mdspan b/tests/integration/deps/mdspan/include/experimental/mdspan
new file mode 100644
index 0000000000000000000000000000000000000000..e8ba715ec2fd7afad65627d2eb4b8fcc720b8a3e
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/experimental/mdspan
@@ -0,0 +1,39 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#pragma once
+
+#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE
+  #define MDSPAN_IMPL_STANDARD_NAMESPACE std
+#endif
+
+#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE
+  #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental
+#endif
+
+#include "../mdspan/mdspan.hpp"
+
+// backward compatibility import into experimental
+namespace MDSPAN_IMPL_STANDARD_NAMESPACE {
+  namespace MDSPAN_IMPL_PROPOSED_NAMESPACE {
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan;
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents;
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_left;
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_right;
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride;
+    using ::MDSPAN_IMPL_STANDARD_NAMESPACE::default_accessor;
+  }
+}
diff --git a/tests/integration/deps/mdspan/include/mdspan/mdarray.hpp b/tests/integration/deps/mdspan/include/mdspan/mdarray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fd8f61c52f1b1836aea89f653e263cd33537e218
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/mdspan/mdarray.hpp
@@ -0,0 +1,31 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#ifndef MDARRAY_HPP_
+#define MDARRAY_HPP_
+
+#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE
+  #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos
+#endif
+
+#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE
+  #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental
+#endif
+
+#include "mdspan.hpp"
+#include "../experimental/__p1684_bits/mdarray.hpp"
+
+#endif // MDARRAY_HPP_
diff --git a/tests/integration/deps/mdspan/include/mdspan/mdspan.hpp b/tests/integration/deps/mdspan/include/mdspan/mdspan.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4a0e354ffd02183e0c738c9c716c1228b3b180bc
--- /dev/null
+++ b/tests/integration/deps/mdspan/include/mdspan/mdspan.hpp
@@ -0,0 +1,43 @@
+//@HEADER
+// ************************************************************************
+//
+//                        Kokkos v. 4.0
+//       Copyright (2022) National Technology & Engineering
+//               Solutions of Sandia, LLC (NTESS).
+//
+// Under the terms of Contract DE-NA0003525 with NTESS,
+// the U.S. Government retains certain rights in this software.
+//
+// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
+// See https://kokkos.org/LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//@HEADER
+
+#ifndef MDSPAN_HPP_
+#define MDSPAN_HPP_
+
+#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE
+  #define MDSPAN_IMPL_STANDARD_NAMESPACE Kokkos
+#endif
+
+#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE
+  #define MDSPAN_IMPL_PROPOSED_NAMESPACE Experimental
+#endif
+
+#include "../experimental/__p0009_bits/default_accessor.hpp"
+#include "../experimental/__p0009_bits/full_extent_t.hpp"
+#include "../experimental/__p0009_bits/mdspan.hpp"
+#include "../experimental/__p0009_bits/dynamic_extent.hpp"
+#include "../experimental/__p0009_bits/extents.hpp"
+#include "../experimental/__p0009_bits/layout_stride.hpp"
+#include "../experimental/__p0009_bits/layout_left.hpp"
+#include "../experimental/__p0009_bits/layout_right.hpp"
+#include "../experimental/__p0009_bits/macros.hpp"
+#if MDSPAN_HAS_CXX_17
+#include "../experimental/__p2642_bits/layout_padded.hpp"
+#include "../experimental/__p2630_bits/submdspan.hpp"
+#endif
+#include "../experimental/__p2389_bits/dims.hpp"
+
+#endif // MDSPAN_HPP_
diff --git a/tests/integration/expected/SimpleClasses.cpp b/tests/integration/expected/SimpleClasses.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9d27ebeddd488ac23d3b87d126f3d17b312e936a
--- /dev/null
+++ b/tests/integration/expected/SimpleClasses.cpp
@@ -0,0 +1,12 @@
+#include <cstdint>
+
+class Point {
+public:
+  const int64_t & getX() const {
+    return this->x;
+  }
+private:
+  int64_t x;
+  int64_t y;
+  int64_t z;
+};
diff --git a/tests/integration/scripts/SimpleClasses.py b/tests/integration/scripts/SimpleClasses.py
new file mode 100644
index 0000000000000000000000000000000000000000..a729d1f125e69c5ef3d178f2ed8be1d65cd423a8
--- /dev/null
+++ b/tests/integration/scripts/SimpleClasses.py
@@ -0,0 +1,18 @@
+from pystencilssfg import SourceFileGenerator
+
+with SourceFileGenerator() as sfg:
+
+    sfg.include("<cstdint>")
+
+    sfg.klass("Point")(
+        sfg.public(
+            sfg.method("getX", returns="const int64_t &", const=True)(
+                "return this->x;"
+            )
+        ),
+        sfg.private(
+            sfg.var("x", "int64_t"),
+            sfg.var("y", "int64_t"),
+            sfg.var("z", "int64_t")
+        )
+    )
diff --git a/tests/integration/scripts/SimpleJacobi.py b/tests/integration/scripts/SimpleJacobi.py
new file mode 100644
index 0000000000000000000000000000000000000000..199419c541fb4aef68d44a1baf7cfc2f28d67e86
--- /dev/null
+++ b/tests/integration/scripts/SimpleJacobi.py
@@ -0,0 +1,23 @@
+import sympy as sp
+
+from pystencils import fields, kernel
+
+from pystencilssfg import SourceFileGenerator
+from pystencilssfg.lang.cpp import mdspan_ref
+
+with SourceFileGenerator() as sfg:
+    u_src, u_dst, f = fields("u_src, u_dst, f(1) : double[2D]", layout="fzyx")
+    h = sp.Symbol("h")
+
+    @kernel
+    def poisson_jacobi():
+        u_dst[0,0] @= (h**2 * f[0, 0] + u_src[1, 0] + u_src[-1, 0] + u_src[0, 1] + u_src[0, -1]) / 4
+
+    poisson_kernel = sfg.kernels.create(poisson_jacobi)
+
+    sfg.function("jacobi_smooth")(
+        sfg.map_field(u_src, mdspan_ref(u_src)),
+        sfg.map_field(u_dst, mdspan_ref(u_dst)),
+        sfg.map_field(f, mdspan_ref(f)),
+        sfg.call(poisson_kernel)
+    )
\ No newline at end of file
diff --git a/tests/integration/test_generator_scripts.py b/tests/integration/test_generator_scripts.py
new file mode 100644
index 0000000000000000000000000000000000000000..33f747820b966bd7a2630e9497cbb00a7937340c
--- /dev/null
+++ b/tests/integration/test_generator_scripts.py
@@ -0,0 +1,81 @@
+import pytest
+
+from dataclasses import dataclass
+
+import os
+from os import path
+import shutil
+import subprocess
+
+THIS_DIR = path.split(__file__)[0]
+SCRIPTS_DIR = path.join(THIS_DIR, "scripts")
+EXPECTED_DIR = path.join(THIS_DIR, "expected")
+
+
+@dataclass
+class ScriptInfo:
+    script_name: str
+    expected_outputs: tuple[str, ...]
+
+    compilable_output: str | None = None
+    compile_cmd: str = f"g++ --std=c++17 -I {THIS_DIR}/deps/mdspan/include"
+
+
+SCRIPTS = [
+    ScriptInfo("SimpleJacobi", ("h", "cpp"), compilable_output="cpp"),
+    ScriptInfo("SimpleClasses", ("h", "cpp")),
+]
+
+
+@pytest.mark.parametrize("script_info", SCRIPTS)
+def test_generator_script(script_info: ScriptInfo):
+    script_name = script_info.script_name
+    script_file = path.join(SCRIPTS_DIR, script_name + ".py")
+
+    output_dir = path.join(THIS_DIR, "out", script_name)
+    if path.exists(output_dir):
+        shutil.rmtree(output_dir)
+    os.makedirs(output_dir, exist_ok=True)
+
+    args = ["python", script_file, "--sfg-output-dir", output_dir]
+
+    result = subprocess.run(args)
+
+    if result.returncode != 0:
+        raise AssertionError(f"Generator script {script_name} failed.")
+
+    #   Check generated files
+    expected_files = set(
+        [f"{script_name}.{ext}" for ext in script_info.expected_outputs]
+    )
+    output_files = set(os.listdir(output_dir))
+    assert output_files == expected_files
+
+    #   Check against expected output
+    for ofile in output_files:
+        expected_file = path.join(EXPECTED_DIR, ofile)
+        actual_file = path.join(output_dir, ofile)
+
+        if not path.exists(expected_file):
+            continue
+
+        with open(expected_file, "r") as f:
+            expected = f.read()
+
+        with open(actual_file, "r") as f:
+            actual = f.read()
+
+        #   Strip whitespace
+        expected = "".join(expected.split())
+        actual = "".join(expected.split())
+
+        assert expected == actual
+
+    #   Check if output compiles
+    if (ext := script_info.compilable_output) is not None:
+        compilable_file = f"{script_name}.{ext}"
+        compile_args = script_info.compile_cmd.split() + ["-c", compilable_file]
+        compile_result = subprocess.run(compile_args, cwd=output_dir)
+
+        if compile_result.returncode != 0:
+            raise AssertionError("Compilation of generated files failed.")
diff --git a/versioneer.py b/versioneer.py
deleted file mode 100644
index 1e3753e63fb6d5a204a7a350b65ed69aa7ab66cd..0000000000000000000000000000000000000000
--- a/versioneer.py
+++ /dev/null
@@ -1,2277 +0,0 @@
-
-# Version: 0.29
-
-"""The Versioneer - like a rocketeer, but for versions.
-
-The Versioneer
-==============
-
-* like a rocketeer, but for versions!
-* https://github.com/python-versioneer/python-versioneer
-* Brian Warner
-* License: Public Domain (Unlicense)
-* Compatible with: Python 3.7, 3.8, 3.9, 3.10, 3.11 and pypy3
-* [![Latest Version][pypi-image]][pypi-url]
-* [![Build Status][travis-image]][travis-url]
-
-This is a tool for managing a recorded version number in setuptools-based
-python projects. The goal is to remove the tedious and error-prone "update
-the embedded version string" step from your release process. Making a new
-release should be as easy as recording a new tag in your version-control
-system, and maybe making new tarballs.
-
-
-## Quick Install
-
-Versioneer provides two installation modes. The "classic" vendored mode installs
-a copy of versioneer into your repository. The experimental build-time dependency mode
-is intended to allow you to skip this step and simplify the process of upgrading.
-
-### Vendored mode
-
-* `pip install versioneer` to somewhere in your $PATH
-   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
-     available, so you can also use `conda install -c conda-forge versioneer`
-* add a `[tool.versioneer]` section to your `pyproject.toml` or a
-  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
-   * Note that you will need to add `tomli; python_version < "3.11"` to your
-     build-time dependencies if you use `pyproject.toml`
-* run `versioneer install --vendor` in your source tree, commit the results
-* verify version information with `python setup.py version`
-
-### Build-time dependency mode
-
-* `pip install versioneer` to somewhere in your $PATH
-   * A [conda-forge recipe](https://github.com/conda-forge/versioneer-feedstock) is
-     available, so you can also use `conda install -c conda-forge versioneer`
-* add a `[tool.versioneer]` section to your `pyproject.toml` or a
-  `[versioneer]` section to your `setup.cfg` (see [Install](INSTALL.md))
-* add `versioneer` (with `[toml]` extra, if configuring in `pyproject.toml`)
-  to the `requires` key of the `build-system` table in `pyproject.toml`:
-  ```toml
-  [build-system]
-  requires = ["setuptools", "versioneer[toml]"]
-  build-backend = "setuptools.build_meta"
-  ```
-* run `versioneer install --no-vendor` in your source tree, commit the results
-* verify version information with `python setup.py version`
-
-## Version Identifiers
-
-Source trees come from a variety of places:
-
-* a version-control system checkout (mostly used by developers)
-* a nightly tarball, produced by build automation
-* a snapshot tarball, produced by a web-based VCS browser, like github's
-  "tarball from tag" feature
-* a release tarball, produced by "setup.py sdist", distributed through PyPI
-
-Within each source tree, the version identifier (either a string or a number,
-this tool is format-agnostic) can come from a variety of places:
-
-* ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows
-  about recent "tags" and an absolute revision-id
-* the name of the directory into which the tarball was unpacked
-* an expanded VCS keyword ($Id$, etc)
-* a `_version.py` created by some earlier build step
-
-For released software, the version identifier is closely related to a VCS
-tag. Some projects use tag names that include more than just the version
-string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool
-needs to strip the tag prefix to extract the version identifier. For
-unreleased software (between tags), the version identifier should provide
-enough information to help developers recreate the same tree, while also
-giving them an idea of roughly how old the tree is (after version 1.2, before
-version 1.3). Many VCS systems can report a description that captures this,
-for example `git describe --tags --dirty --always` reports things like
-"0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the
-0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has
-uncommitted changes).
-
-The version identifier is used for multiple purposes:
-
-* to allow the module to self-identify its version: `myproject.__version__`
-* to choose a name and prefix for a 'setup.py sdist' tarball
-
-## Theory of Operation
-
-Versioneer works by adding a special `_version.py` file into your source
-tree, where your `__init__.py` can import it. This `_version.py` knows how to
-dynamically ask the VCS tool for version information at import time.
-
-`_version.py` also contains `$Revision$` markers, and the installation
-process marks `_version.py` to have this marker rewritten with a tag name
-during the `git archive` command. As a result, generated tarballs will
-contain enough information to get the proper version.
-
-To allow `setup.py` to compute a version too, a `versioneer.py` is added to
-the top level of your source tree, next to `setup.py` and the `setup.cfg`
-that configures it. This overrides several distutils/setuptools commands to
-compute the version when invoked, and changes `setup.py build` and `setup.py
-sdist` to replace `_version.py` with a small static file that contains just
-the generated version data.
-
-## Installation
-
-See [INSTALL.md](./INSTALL.md) for detailed installation instructions.
-
-## Version-String Flavors
-
-Code which uses Versioneer can learn about its version string at runtime by
-importing `_version` from your main `__init__.py` file and running the
-`get_versions()` function. From the "outside" (e.g. in `setup.py`), you can
-import the top-level `versioneer.py` and run `get_versions()`.
-
-Both functions return a dictionary with different flavors of version
-information:
-
-* `['version']`: A condensed version string, rendered using the selected
-  style. This is the most commonly used value for the project's version
-  string. The default "pep440" style yields strings like `0.11`,
-  `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section
-  below for alternative styles.
-
-* `['full-revisionid']`: detailed revision identifier. For Git, this is the
-  full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac".
-
-* `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the
-  commit date in ISO 8601 format. This will be None if the date is not
-  available.
-
-* `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that
-  this is only accurate if run in a VCS checkout, otherwise it is likely to
-  be False or None
-
-* `['error']`: if the version string could not be computed, this will be set
-  to a string describing the problem, otherwise it will be None. It may be
-  useful to throw an exception in setup.py if this is set, to avoid e.g.
-  creating tarballs with a version string of "unknown".
-
-Some variants are more useful than others. Including `full-revisionid` in a
-bug report should allow developers to reconstruct the exact code being tested
-(or indicate the presence of local changes that should be shared with the
-developers). `version` is suitable for display in an "about" box or a CLI
-`--version` output: it can be easily compared against release notes and lists
-of bugs fixed in various releases.
-
-The installer adds the following text to your `__init__.py` to place a basic
-version in `YOURPROJECT.__version__`:
-
-    from ._version import get_versions
-    __version__ = get_versions()['version']
-    del get_versions
-
-## Styles
-
-The setup.cfg `style=` configuration controls how the VCS information is
-rendered into a version string.
-
-The default style, "pep440", produces a PEP440-compliant string, equal to the
-un-prefixed tag name for actual releases, and containing an additional "local
-version" section with more detail for in-between builds. For Git, this is
-TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags
---dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the
-tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and
-that this commit is two revisions ("+2") beyond the "0.11" tag. For released
-software (exactly equal to a known tag), the identifier will only contain the
-stripped tag, e.g. "0.11".
-
-Other styles are available. See [details.md](details.md) in the Versioneer
-source tree for descriptions.
-
-## Debugging
-
-Versioneer tries to avoid fatal errors: if something goes wrong, it will tend
-to return a version of "0+unknown". To investigate the problem, run `setup.py
-version`, which will run the version-lookup code in a verbose mode, and will
-display the full contents of `get_versions()` (including the `error` string,
-which may help identify what went wrong).
-
-## Known Limitations
-
-Some situations are known to cause problems for Versioneer. This details the
-most significant ones. More can be found on Github
-[issues page](https://github.com/python-versioneer/python-versioneer/issues).
-
-### Subprojects
-
-Versioneer has limited support for source trees in which `setup.py` is not in
-the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are
-two common reasons why `setup.py` might not be in the root:
-
-* Source trees which contain multiple subprojects, such as
-  [Buildbot](https://github.com/buildbot/buildbot), which contains both
-  "master" and "slave" subprojects, each with their own `setup.py`,
-  `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI
-  distributions (and upload multiple independently-installable tarballs).
-* Source trees whose main purpose is to contain a C library, but which also
-  provide bindings to Python (and perhaps other languages) in subdirectories.
-
-Versioneer will look for `.git` in parent directories, and most operations
-should get the right version string. However `pip` and `setuptools` have bugs
-and implementation details which frequently cause `pip install .` from a
-subproject directory to fail to find a correct version string (so it usually
-defaults to `0+unknown`).
-
-`pip install --editable .` should work correctly. `setup.py install` might
-work too.
-
-Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in
-some later version.
-
-[Bug #38](https://github.com/python-versioneer/python-versioneer/issues/38) is tracking
-this issue. The discussion in
-[PR #61](https://github.com/python-versioneer/python-versioneer/pull/61) describes the
-issue from the Versioneer side in more detail.
-[pip PR#3176](https://github.com/pypa/pip/pull/3176) and
-[pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve
-pip to let Versioneer work correctly.
-
-Versioneer-0.16 and earlier only looked for a `.git` directory next to the
-`setup.cfg`, so subprojects were completely unsupported with those releases.
-
-### Editable installs with setuptools <= 18.5
-
-`setup.py develop` and `pip install --editable .` allow you to install a
-project into a virtualenv once, then continue editing the source code (and
-test) without re-installing after every change.
-
-"Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a
-convenient way to specify executable scripts that should be installed along
-with the python package.
-
-These both work as expected when using modern setuptools. When using
-setuptools-18.5 or earlier, however, certain operations will cause
-`pkg_resources.DistributionNotFound` errors when running the entrypoint
-script, which must be resolved by re-installing the package. This happens
-when the install happens with one version, then the egg_info data is
-regenerated while a different version is checked out. Many setup.py commands
-cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into
-a different virtualenv), so this can be surprising.
-
-[Bug #83](https://github.com/python-versioneer/python-versioneer/issues/83) describes
-this one, but upgrading to a newer version of setuptools should probably
-resolve it.
-
-
-## Updating Versioneer
-
-To upgrade your project to a new release of Versioneer, do the following:
-
-* install the new Versioneer (`pip install -U versioneer` or equivalent)
-* edit `setup.cfg` and `pyproject.toml`, if necessary,
-  to include any new configuration settings indicated by the release notes.
-  See [UPGRADING](./UPGRADING.md) for details.
-* re-run `versioneer install --[no-]vendor` in your source tree, to replace
-  `SRC/_version.py`
-* commit any changed files
-
-## Future Directions
-
-This tool is designed to make it easily extended to other version-control
-systems: all VCS-specific components are in separate directories like
-src/git/ . The top-level `versioneer.py` script is assembled from these
-components by running make-versioneer.py . In the future, make-versioneer.py
-will take a VCS name as an argument, and will construct a version of
-`versioneer.py` that is specific to the given VCS. It might also take the
-configuration arguments that are currently provided manually during
-installation by editing setup.py . Alternatively, it might go the other
-direction and include code from all supported VCS systems, reducing the
-number of intermediate scripts.
-
-## Similar projects
-
-* [setuptools_scm](https://github.com/pypa/setuptools_scm/) - a non-vendored build-time
-  dependency
-* [minver](https://github.com/jbweston/miniver) - a lightweight reimplementation of
-  versioneer
-* [versioningit](https://github.com/jwodder/versioningit) - a PEP 518-based setuptools
-  plugin
-
-## License
-
-To make Versioneer easier to embed, all its code is dedicated to the public
-domain. The `_version.py` that it creates is also in the public domain.
-Specifically, both are released under the "Unlicense", as described in
-https://unlicense.org/.
-
-[pypi-image]: https://img.shields.io/pypi/v/versioneer.svg
-[pypi-url]: https://pypi.python.org/pypi/versioneer/
-[travis-image]:
-https://img.shields.io/travis/com/python-versioneer/python-versioneer.svg
-[travis-url]: https://travis-ci.com/github/python-versioneer/python-versioneer
-
-"""
-# pylint:disable=invalid-name,import-outside-toplevel,missing-function-docstring
-# pylint:disable=missing-class-docstring,too-many-branches,too-many-statements
-# pylint:disable=raise-missing-from,too-many-lines,too-many-locals,import-error
-# pylint:disable=too-few-public-methods,redefined-outer-name,consider-using-with
-# pylint:disable=attribute-defined-outside-init,too-many-arguments
-
-import configparser
-import errno
-import json
-import os
-import re
-import subprocess
-import sys
-from pathlib import Path
-from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union
-from typing import NoReturn
-import functools
-
-have_tomllib = True
-if sys.version_info >= (3, 11):
-    import tomllib
-else:
-    try:
-        import tomli as tomllib
-    except ImportError:
-        have_tomllib = False
-
-
-class VersioneerConfig:
-    """Container for Versioneer configuration parameters."""
-
-    VCS: str
-    style: str
-    tag_prefix: str
-    versionfile_source: str
-    versionfile_build: Optional[str]
-    parentdir_prefix: Optional[str]
-    verbose: Optional[bool]
-
-
-def get_root() -> str:
-    """Get the project root directory.
-
-    We require that all commands are run from the project root, i.e. the
-    directory that contains setup.py, setup.cfg, and versioneer.py .
-    """
-    root = os.path.realpath(os.path.abspath(os.getcwd()))
-    setup_py = os.path.join(root, "setup.py")
-    pyproject_toml = os.path.join(root, "pyproject.toml")
-    versioneer_py = os.path.join(root, "versioneer.py")
-    if not (
-        os.path.exists(setup_py)
-        or os.path.exists(pyproject_toml)
-        or os.path.exists(versioneer_py)
-    ):
-        # allow 'python path/to/setup.py COMMAND'
-        root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0])))
-        setup_py = os.path.join(root, "setup.py")
-        pyproject_toml = os.path.join(root, "pyproject.toml")
-        versioneer_py = os.path.join(root, "versioneer.py")
-    if not (
-        os.path.exists(setup_py)
-        or os.path.exists(pyproject_toml)
-        or os.path.exists(versioneer_py)
-    ):
-        err = ("Versioneer was unable to run the project root directory. "
-               "Versioneer requires setup.py to be executed from "
-               "its immediate directory (like 'python setup.py COMMAND'), "
-               "or in a way that lets it use sys.argv[0] to find the root "
-               "(like 'python path/to/setup.py COMMAND').")
-        raise VersioneerBadRootError(err)
-    try:
-        # Certain runtime workflows (setup.py install/develop in a setuptools
-        # tree) execute all dependencies in a single python process, so
-        # "versioneer" may be imported multiple times, and python's shared
-        # module-import table will cache the first one. So we can't use
-        # os.path.dirname(__file__), as that will find whichever
-        # versioneer.py was first imported, even in later projects.
-        my_path = os.path.realpath(os.path.abspath(__file__))
-        me_dir = os.path.normcase(os.path.splitext(my_path)[0])
-        vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0])
-        if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals():
-            print("Warning: build in %s is using versioneer.py from %s"
-                  % (os.path.dirname(my_path), versioneer_py))
-    except NameError:
-        pass
-    return root
-
-
-def get_config_from_root(root: str) -> VersioneerConfig:
-    """Read the project setup.cfg file to determine Versioneer config."""
-    # This might raise OSError (if setup.cfg is missing), or
-    # configparser.NoSectionError (if it lacks a [versioneer] section), or
-    # configparser.NoOptionError (if it lacks "VCS="). See the docstring at
-    # the top of versioneer.py for instructions on writing your setup.cfg .
-    root_pth = Path(root)
-    pyproject_toml = root_pth / "pyproject.toml"
-    setup_cfg = root_pth / "setup.cfg"
-    section: Union[Dict[str, Any], configparser.SectionProxy, None] = None
-    if pyproject_toml.exists() and have_tomllib:
-        try:
-            with open(pyproject_toml, 'rb') as fobj:
-                pp = tomllib.load(fobj)
-            section = pp['tool']['versioneer']
-        except (tomllib.TOMLDecodeError, KeyError) as e:
-            print(f"Failed to load config from {pyproject_toml}: {e}")
-            print("Try to load it from setup.cfg")
-    if not section:
-        parser = configparser.ConfigParser()
-        with open(setup_cfg) as cfg_file:
-            parser.read_file(cfg_file)
-        parser.get("versioneer", "VCS")  # raise error if missing
-
-        section = parser["versioneer"]
-
-    # `cast`` really shouldn't be used, but its simplest for the
-    # common VersioneerConfig users at the moment. We verify against
-    # `None` values elsewhere where it matters
-
-    cfg = VersioneerConfig()
-    cfg.VCS = section['VCS']
-    cfg.style = section.get("style", "")
-    cfg.versionfile_source = cast(str, section.get("versionfile_source"))
-    cfg.versionfile_build = section.get("versionfile_build")
-    cfg.tag_prefix = cast(str, section.get("tag_prefix"))
-    if cfg.tag_prefix in ("''", '""', None):
-        cfg.tag_prefix = ""
-    cfg.parentdir_prefix = section.get("parentdir_prefix")
-    if isinstance(section, configparser.SectionProxy):
-        # Make sure configparser translates to bool
-        cfg.verbose = section.getboolean("verbose")
-    else:
-        cfg.verbose = section.get("verbose")
-
-    return cfg
-
-
-class NotThisMethod(Exception):
-    """Exception raised if a method is not valid for the current scenario."""
-
-
-# these dictionaries contain VCS-specific tools
-LONG_VERSION_PY: Dict[str, str] = {}
-HANDLERS: Dict[str, Dict[str, Callable]] = {}
-
-
-def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
-    """Create decorator to mark a method as the handler of a VCS."""
-    def decorate(f: Callable) -> Callable:
-        """Store f in HANDLERS[vcs][method]."""
-        HANDLERS.setdefault(vcs, {})[method] = f
-        return f
-    return decorate
-
-
-def run_command(
-    commands: List[str],
-    args: List[str],
-    cwd: Optional[str] = None,
-    verbose: bool = False,
-    hide_stderr: bool = False,
-    env: Optional[Dict[str, str]] = None,
-) -> Tuple[Optional[str], Optional[int]]:
-    """Call the given command(s)."""
-    assert isinstance(commands, list)
-    process = None
-
-    popen_kwargs: Dict[str, Any] = {}
-    if sys.platform == "win32":
-        # This hides the console window if pythonw.exe is used
-        startupinfo = subprocess.STARTUPINFO()
-        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-        popen_kwargs["startupinfo"] = startupinfo
-
-    for command in commands:
-        try:
-            dispcmd = str([command] + args)
-            # remember shell=False, so use git.cmd on windows, not just git
-            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
-                                       stdout=subprocess.PIPE,
-                                       stderr=(subprocess.PIPE if hide_stderr
-                                               else None), **popen_kwargs)
-            break
-        except OSError as e:
-            if e.errno == errno.ENOENT:
-                continue
-            if verbose:
-                print("unable to run %s" % dispcmd)
-                print(e)
-            return None, None
-    else:
-        if verbose:
-            print("unable to find command, tried %s" % (commands,))
-        return None, None
-    stdout = process.communicate()[0].strip().decode()
-    if process.returncode != 0:
-        if verbose:
-            print("unable to run %s (error)" % dispcmd)
-            print("stdout was %s" % stdout)
-        return None, process.returncode
-    return stdout, process.returncode
-
-
-LONG_VERSION_PY['git'] = r'''
-# This file helps to compute a version number in source trees obtained from
-# git-archive tarball (such as those provided by githubs download-from-tag
-# feature). Distribution tarballs (built by setup.py sdist) and build
-# directories (produced by setup.py build) will contain a much shorter file
-# that just contains the computed version number.
-
-# This file is released into the public domain.
-# Generated by versioneer-0.29
-# https://github.com/python-versioneer/python-versioneer
-
-"""Git implementation of _version.py."""
-
-import errno
-import os
-import re
-import subprocess
-import sys
-from typing import Any, Callable, Dict, List, Optional, Tuple
-import functools
-
-
-def get_keywords() -> Dict[str, str]:
-    """Get the keywords needed to look up the version information."""
-    # these strings will be replaced by git during git-archive.
-    # setup.py/versioneer.py will grep for the variable names, so they must
-    # each be defined on a line of their own. _version.py will just call
-    # get_keywords().
-    git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s"
-    git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s"
-    git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s"
-    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
-    return keywords
-
-
-class VersioneerConfig:
-    """Container for Versioneer configuration parameters."""
-
-    VCS: str
-    style: str
-    tag_prefix: str
-    parentdir_prefix: str
-    versionfile_source: str
-    verbose: bool
-
-
-def get_config() -> VersioneerConfig:
-    """Create, populate and return the VersioneerConfig() object."""
-    # these strings are filled in when 'setup.py versioneer' creates
-    # _version.py
-    cfg = VersioneerConfig()
-    cfg.VCS = "git"
-    cfg.style = "%(STYLE)s"
-    cfg.tag_prefix = "%(TAG_PREFIX)s"
-    cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s"
-    cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s"
-    cfg.verbose = False
-    return cfg
-
-
-class NotThisMethod(Exception):
-    """Exception raised if a method is not valid for the current scenario."""
-
-
-LONG_VERSION_PY: Dict[str, str] = {}
-HANDLERS: Dict[str, Dict[str, Callable]] = {}
-
-
-def register_vcs_handler(vcs: str, method: str) -> Callable:  # decorator
-    """Create decorator to mark a method as the handler of a VCS."""
-    def decorate(f: Callable) -> Callable:
-        """Store f in HANDLERS[vcs][method]."""
-        if vcs not in HANDLERS:
-            HANDLERS[vcs] = {}
-        HANDLERS[vcs][method] = f
-        return f
-    return decorate
-
-
-def run_command(
-    commands: List[str],
-    args: List[str],
-    cwd: Optional[str] = None,
-    verbose: bool = False,
-    hide_stderr: bool = False,
-    env: Optional[Dict[str, str]] = None,
-) -> Tuple[Optional[str], Optional[int]]:
-    """Call the given command(s)."""
-    assert isinstance(commands, list)
-    process = None
-
-    popen_kwargs: Dict[str, Any] = {}
-    if sys.platform == "win32":
-        # This hides the console window if pythonw.exe is used
-        startupinfo = subprocess.STARTUPINFO()
-        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-        popen_kwargs["startupinfo"] = startupinfo
-
-    for command in commands:
-        try:
-            dispcmd = str([command] + args)
-            # remember shell=False, so use git.cmd on windows, not just git
-            process = subprocess.Popen([command] + args, cwd=cwd, env=env,
-                                       stdout=subprocess.PIPE,
-                                       stderr=(subprocess.PIPE if hide_stderr
-                                               else None), **popen_kwargs)
-            break
-        except OSError as e:
-            if e.errno == errno.ENOENT:
-                continue
-            if verbose:
-                print("unable to run %%s" %% dispcmd)
-                print(e)
-            return None, None
-    else:
-        if verbose:
-            print("unable to find command, tried %%s" %% (commands,))
-        return None, None
-    stdout = process.communicate()[0].strip().decode()
-    if process.returncode != 0:
-        if verbose:
-            print("unable to run %%s (error)" %% dispcmd)
-            print("stdout was %%s" %% stdout)
-        return None, process.returncode
-    return stdout, process.returncode
-
-
-def versions_from_parentdir(
-    parentdir_prefix: str,
-    root: str,
-    verbose: bool,
-) -> Dict[str, Any]:
-    """Try to determine the version from the parent directory name.
-
-    Source tarballs conventionally unpack into a directory that includes both
-    the project name and a version string. We will also support searching up
-    two directory levels for an appropriately named parent directory
-    """
-    rootdirs = []
-
-    for _ in range(3):
-        dirname = os.path.basename(root)
-        if dirname.startswith(parentdir_prefix):
-            return {"version": dirname[len(parentdir_prefix):],
-                    "full-revisionid": None,
-                    "dirty": False, "error": None, "date": None}
-        rootdirs.append(root)
-        root = os.path.dirname(root)  # up a level
-
-    if verbose:
-        print("Tried directories %%s but none started with prefix %%s" %%
-              (str(rootdirs), parentdir_prefix))
-    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
-
-
-@register_vcs_handler("git", "get_keywords")
-def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
-    """Extract version information from the given file."""
-    # the code embedded in _version.py can just fetch the value of these
-    # keywords. When used from setup.py, we don't want to import _version.py,
-    # so we do it with a regexp instead. This function is not used from
-    # _version.py.
-    keywords: Dict[str, str] = {}
-    try:
-        with open(versionfile_abs, "r") as fobj:
-            for line in fobj:
-                if line.strip().startswith("git_refnames ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["refnames"] = mo.group(1)
-                if line.strip().startswith("git_full ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["full"] = mo.group(1)
-                if line.strip().startswith("git_date ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["date"] = mo.group(1)
-    except OSError:
-        pass
-    return keywords
-
-
-@register_vcs_handler("git", "keywords")
-def git_versions_from_keywords(
-    keywords: Dict[str, str],
-    tag_prefix: str,
-    verbose: bool,
-) -> Dict[str, Any]:
-    """Get version information from git keywords."""
-    if "refnames" not in keywords:
-        raise NotThisMethod("Short version file found")
-    date = keywords.get("date")
-    if date is not None:
-        # Use only the last line.  Previous lines may contain GPG signature
-        # information.
-        date = date.splitlines()[-1]
-
-        # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant
-        # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601
-        # -like" string, which we must then edit to make compliant), because
-        # it's been around since git-1.5.3, and it's too difficult to
-        # discover which version we're using, or to work around using an
-        # older one.
-        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
-    refnames = keywords["refnames"].strip()
-    if refnames.startswith("$Format"):
-        if verbose:
-            print("keywords are unexpanded, not using")
-        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
-    refs = {r.strip() for r in refnames.strip("()").split(",")}
-    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
-    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
-    TAG = "tag: "
-    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
-    if not tags:
-        # Either we're using git < 1.8.3, or there really are no tags. We use
-        # a heuristic: assume all version tags have a digit. The old git %%d
-        # expansion behaves like git log --decorate=short and strips out the
-        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
-        # between branches and tags. By ignoring refnames without digits, we
-        # filter out many common branch names like "release" and
-        # "stabilization", as well as "HEAD" and "master".
-        tags = {r for r in refs if re.search(r'\d', r)}
-        if verbose:
-            print("discarding '%%s', no digits" %% ",".join(refs - tags))
-    if verbose:
-        print("likely tags: %%s" %% ",".join(sorted(tags)))
-    for ref in sorted(tags):
-        # sorting will prefer e.g. "2.0" over "2.0rc1"
-        if ref.startswith(tag_prefix):
-            r = ref[len(tag_prefix):]
-            # Filter out refs that exactly match prefix or that don't start
-            # with a number once the prefix is stripped (mostly a concern
-            # when prefix is '')
-            if not re.match(r'\d', r):
-                continue
-            if verbose:
-                print("picking %%s" %% r)
-            return {"version": r,
-                    "full-revisionid": keywords["full"].strip(),
-                    "dirty": False, "error": None,
-                    "date": date}
-    # no suitable tags, so version is "0+unknown", but full hex is still there
-    if verbose:
-        print("no suitable tags, using unknown + full revision id")
-    return {"version": "0+unknown",
-            "full-revisionid": keywords["full"].strip(),
-            "dirty": False, "error": "no suitable tags", "date": None}
-
-
-@register_vcs_handler("git", "pieces_from_vcs")
-def git_pieces_from_vcs(
-    tag_prefix: str,
-    root: str,
-    verbose: bool,
-    runner: Callable = run_command
-) -> Dict[str, Any]:
-    """Get version from 'git describe' in the root of the source tree.
-
-    This only gets called if the git-archive 'subst' keywords were *not*
-    expanded, and _version.py hasn't already been rewritten with a short
-    version string, meaning we're inside a checked out source tree.
-    """
-    GITS = ["git"]
-    if sys.platform == "win32":
-        GITS = ["git.cmd", "git.exe"]
-
-    # GIT_DIR can interfere with correct operation of Versioneer.
-    # It may be intended to be passed to the Versioneer-versioned project,
-    # but that should not change where we get our version from.
-    env = os.environ.copy()
-    env.pop("GIT_DIR", None)
-    runner = functools.partial(runner, env=env)
-
-    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
-                   hide_stderr=not verbose)
-    if rc != 0:
-        if verbose:
-            print("Directory %%s not under git control" %% root)
-        raise NotThisMethod("'git rev-parse --git-dir' returned error")
-
-    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
-    # if there isn't one, this yields HEX[-dirty] (no NUM)
-    describe_out, rc = runner(GITS, [
-        "describe", "--tags", "--dirty", "--always", "--long",
-        "--match", f"{tag_prefix}[[:digit:]]*"
-    ], cwd=root)
-    # --long was added in git-1.5.5
-    if describe_out is None:
-        raise NotThisMethod("'git describe' failed")
-    describe_out = describe_out.strip()
-    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
-    if full_out is None:
-        raise NotThisMethod("'git rev-parse' failed")
-    full_out = full_out.strip()
-
-    pieces: Dict[str, Any] = {}
-    pieces["long"] = full_out
-    pieces["short"] = full_out[:7]  # maybe improved later
-    pieces["error"] = None
-
-    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
-                             cwd=root)
-    # --abbrev-ref was added in git-1.6.3
-    if rc != 0 or branch_name is None:
-        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
-    branch_name = branch_name.strip()
-
-    if branch_name == "HEAD":
-        # If we aren't exactly on a branch, pick a branch which represents
-        # the current commit. If all else fails, we are on a branchless
-        # commit.
-        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
-        # --contains was added in git-1.5.4
-        if rc != 0 or branches is None:
-            raise NotThisMethod("'git branch --contains' returned error")
-        branches = branches.split("\n")
-
-        # Remove the first line if we're running detached
-        if "(" in branches[0]:
-            branches.pop(0)
-
-        # Strip off the leading "* " from the list of branches.
-        branches = [branch[2:] for branch in branches]
-        if "master" in branches:
-            branch_name = "master"
-        elif not branches:
-            branch_name = None
-        else:
-            # Pick the first branch that is returned. Good or bad.
-            branch_name = branches[0]
-
-    pieces["branch"] = branch_name
-
-    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
-    # TAG might have hyphens.
-    git_describe = describe_out
-
-    # look for -dirty suffix
-    dirty = git_describe.endswith("-dirty")
-    pieces["dirty"] = dirty
-    if dirty:
-        git_describe = git_describe[:git_describe.rindex("-dirty")]
-
-    # now we have TAG-NUM-gHEX or HEX
-
-    if "-" in git_describe:
-        # TAG-NUM-gHEX
-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
-        if not mo:
-            # unparsable. Maybe git-describe is misbehaving?
-            pieces["error"] = ("unable to parse git-describe output: '%%s'"
-                               %% describe_out)
-            return pieces
-
-        # tag
-        full_tag = mo.group(1)
-        if not full_tag.startswith(tag_prefix):
-            if verbose:
-                fmt = "tag '%%s' doesn't start with prefix '%%s'"
-                print(fmt %% (full_tag, tag_prefix))
-            pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'"
-                               %% (full_tag, tag_prefix))
-            return pieces
-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
-
-        # distance: number of commits since tag
-        pieces["distance"] = int(mo.group(2))
-
-        # commit: short hex revision ID
-        pieces["short"] = mo.group(3)
-
-    else:
-        # HEX: no tags
-        pieces["closest-tag"] = None
-        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
-        pieces["distance"] = len(out.split())  # total number of commits
-
-    # commit date: see ISO-8601 comment in git_versions_from_keywords()
-    date = runner(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip()
-    # Use only the last line.  Previous lines may contain GPG signature
-    # information.
-    date = date.splitlines()[-1]
-    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
-
-    return pieces
-
-
-def plus_or_dot(pieces: Dict[str, Any]) -> str:
-    """Return a + if we don't already have one, else return a ."""
-    if "+" in pieces.get("closest-tag", ""):
-        return "."
-    return "+"
-
-
-def render_pep440(pieces: Dict[str, Any]) -> str:
-    """Build up version string, with post-release "local version identifier".
-
-    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
-    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
-
-    Exceptions:
-    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += plus_or_dot(pieces)
-            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"],
-                                          pieces["short"])
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def render_pep440_branch(pieces: Dict[str, Any]) -> str:
-    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
-
-    The ".dev0" means not master branch. Note that .dev0 sorts backwards
-    (a feature branch will appear "older" than the master branch).
-
-    Exceptions:
-    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            if pieces["branch"] != "master":
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"])
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0"
-        if pieces["branch"] != "master":
-            rendered += ".dev0"
-        rendered += "+untagged.%%d.g%%s" %% (pieces["distance"],
-                                          pieces["short"])
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
-    """Split pep440 version string at the post-release segment.
-
-    Returns the release segments before the post-release and the
-    post-release version number (or -1 if no post-release segment is present).
-    """
-    vc = str.split(ver, ".post")
-    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
-
-
-def render_pep440_pre(pieces: Dict[str, Any]) -> str:
-    """TAG[.postN.devDISTANCE] -- No -dirty.
-
-    Exceptions:
-    1: no tags. 0.post0.devDISTANCE
-    """
-    if pieces["closest-tag"]:
-        if pieces["distance"]:
-            # update the post release segment
-            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
-            rendered = tag_version
-            if post_version is not None:
-                rendered += ".post%%d.dev%%d" %% (post_version + 1, pieces["distance"])
-            else:
-                rendered += ".post0.dev%%d" %% (pieces["distance"])
-        else:
-            # no commits, use the tag as the version
-            rendered = pieces["closest-tag"]
-    else:
-        # exception #1
-        rendered = "0.post0.dev%%d" %% pieces["distance"]
-    return rendered
-
-
-def render_pep440_post(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]+gHEX] .
-
-    The ".dev0" means dirty. Note that .dev0 sorts backwards
-    (a dirty tree will appear "older" than the corresponding clean one),
-    but you shouldn't be releasing software with -dirty anyways.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%%d" %% pieces["distance"]
-            if pieces["dirty"]:
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "g%%s" %% pieces["short"]
-    else:
-        # exception #1
-        rendered = "0.post%%d" %% pieces["distance"]
-        if pieces["dirty"]:
-            rendered += ".dev0"
-        rendered += "+g%%s" %% pieces["short"]
-    return rendered
-
-
-def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
-
-    The ".dev0" means not master branch.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%%d" %% pieces["distance"]
-            if pieces["branch"] != "master":
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "g%%s" %% pieces["short"]
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0.post%%d" %% pieces["distance"]
-        if pieces["branch"] != "master":
-            rendered += ".dev0"
-        rendered += "+g%%s" %% pieces["short"]
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def render_pep440_old(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]] .
-
-    The ".dev0" means dirty.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%%d" %% pieces["distance"]
-            if pieces["dirty"]:
-                rendered += ".dev0"
-    else:
-        # exception #1
-        rendered = "0.post%%d" %% pieces["distance"]
-        if pieces["dirty"]:
-            rendered += ".dev0"
-    return rendered
-
-
-def render_git_describe(pieces: Dict[str, Any]) -> str:
-    """TAG[-DISTANCE-gHEX][-dirty].
-
-    Like 'git describe --tags --dirty --always'.
-
-    Exceptions:
-    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"]:
-            rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
-    else:
-        # exception #1
-        rendered = pieces["short"]
-    if pieces["dirty"]:
-        rendered += "-dirty"
-    return rendered
-
-
-def render_git_describe_long(pieces: Dict[str, Any]) -> str:
-    """TAG-DISTANCE-gHEX[-dirty].
-
-    Like 'git describe --tags --dirty --always -long'.
-    The distance/hash is unconditional.
-
-    Exceptions:
-    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"])
-    else:
-        # exception #1
-        rendered = pieces["short"]
-    if pieces["dirty"]:
-        rendered += "-dirty"
-    return rendered
-
-
-def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
-    """Render the given version pieces into the requested style."""
-    if pieces["error"]:
-        return {"version": "unknown",
-                "full-revisionid": pieces.get("long"),
-                "dirty": None,
-                "error": pieces["error"],
-                "date": None}
-
-    if not style or style == "default":
-        style = "pep440"  # the default
-
-    if style == "pep440":
-        rendered = render_pep440(pieces)
-    elif style == "pep440-branch":
-        rendered = render_pep440_branch(pieces)
-    elif style == "pep440-pre":
-        rendered = render_pep440_pre(pieces)
-    elif style == "pep440-post":
-        rendered = render_pep440_post(pieces)
-    elif style == "pep440-post-branch":
-        rendered = render_pep440_post_branch(pieces)
-    elif style == "pep440-old":
-        rendered = render_pep440_old(pieces)
-    elif style == "git-describe":
-        rendered = render_git_describe(pieces)
-    elif style == "git-describe-long":
-        rendered = render_git_describe_long(pieces)
-    else:
-        raise ValueError("unknown style '%%s'" %% style)
-
-    return {"version": rendered, "full-revisionid": pieces["long"],
-            "dirty": pieces["dirty"], "error": None,
-            "date": pieces.get("date")}
-
-
-def get_versions() -> Dict[str, Any]:
-    """Get version information or return default if unable to do so."""
-    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
-    # __file__, we can work backwards from there to the root. Some
-    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
-    # case we can only use expanded keywords.
-
-    cfg = get_config()
-    verbose = cfg.verbose
-
-    try:
-        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
-                                          verbose)
-    except NotThisMethod:
-        pass
-
-    try:
-        root = os.path.realpath(__file__)
-        # versionfile_source is the relative path from the top of the source
-        # tree (where the .git directory might live) to this file. Invert
-        # this to find the root from __file__.
-        for _ in cfg.versionfile_source.split('/'):
-            root = os.path.dirname(root)
-    except NameError:
-        return {"version": "0+unknown", "full-revisionid": None,
-                "dirty": None,
-                "error": "unable to find root of source tree",
-                "date": None}
-
-    try:
-        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
-        return render(pieces, cfg.style)
-    except NotThisMethod:
-        pass
-
-    try:
-        if cfg.parentdir_prefix:
-            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
-    except NotThisMethod:
-        pass
-
-    return {"version": "0+unknown", "full-revisionid": None,
-            "dirty": None,
-            "error": "unable to compute version", "date": None}
-'''
-
-
-@register_vcs_handler("git", "get_keywords")
-def git_get_keywords(versionfile_abs: str) -> Dict[str, str]:
-    """Extract version information from the given file."""
-    # the code embedded in _version.py can just fetch the value of these
-    # keywords. When used from setup.py, we don't want to import _version.py,
-    # so we do it with a regexp instead. This function is not used from
-    # _version.py.
-    keywords: Dict[str, str] = {}
-    try:
-        with open(versionfile_abs, "r") as fobj:
-            for line in fobj:
-                if line.strip().startswith("git_refnames ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["refnames"] = mo.group(1)
-                if line.strip().startswith("git_full ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["full"] = mo.group(1)
-                if line.strip().startswith("git_date ="):
-                    mo = re.search(r'=\s*"(.*)"', line)
-                    if mo:
-                        keywords["date"] = mo.group(1)
-    except OSError:
-        pass
-    return keywords
-
-
-@register_vcs_handler("git", "keywords")
-def git_versions_from_keywords(
-    keywords: Dict[str, str],
-    tag_prefix: str,
-    verbose: bool,
-) -> Dict[str, Any]:
-    """Get version information from git keywords."""
-    if "refnames" not in keywords:
-        raise NotThisMethod("Short version file found")
-    date = keywords.get("date")
-    if date is not None:
-        # Use only the last line.  Previous lines may contain GPG signature
-        # information.
-        date = date.splitlines()[-1]
-
-        # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
-        # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
-        # -like" string, which we must then edit to make compliant), because
-        # it's been around since git-1.5.3, and it's too difficult to
-        # discover which version we're using, or to work around using an
-        # older one.
-        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
-    refnames = keywords["refnames"].strip()
-    if refnames.startswith("$Format"):
-        if verbose:
-            print("keywords are unexpanded, not using")
-        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
-    refs = {r.strip() for r in refnames.strip("()").split(",")}
-    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
-    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
-    TAG = "tag: "
-    tags = {r[len(TAG):] for r in refs if r.startswith(TAG)}
-    if not tags:
-        # Either we're using git < 1.8.3, or there really are no tags. We use
-        # a heuristic: assume all version tags have a digit. The old git %d
-        # expansion behaves like git log --decorate=short and strips out the
-        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
-        # between branches and tags. By ignoring refnames without digits, we
-        # filter out many common branch names like "release" and
-        # "stabilization", as well as "HEAD" and "master".
-        tags = {r for r in refs if re.search(r'\d', r)}
-        if verbose:
-            print("discarding '%s', no digits" % ",".join(refs - tags))
-    if verbose:
-        print("likely tags: %s" % ",".join(sorted(tags)))
-    for ref in sorted(tags):
-        # sorting will prefer e.g. "2.0" over "2.0rc1"
-        if ref.startswith(tag_prefix):
-            r = ref[len(tag_prefix):]
-            # Filter out refs that exactly match prefix or that don't start
-            # with a number once the prefix is stripped (mostly a concern
-            # when prefix is '')
-            if not re.match(r'\d', r):
-                continue
-            if verbose:
-                print("picking %s" % r)
-            return {"version": r,
-                    "full-revisionid": keywords["full"].strip(),
-                    "dirty": False, "error": None,
-                    "date": date}
-    # no suitable tags, so version is "0+unknown", but full hex is still there
-    if verbose:
-        print("no suitable tags, using unknown + full revision id")
-    return {"version": "0+unknown",
-            "full-revisionid": keywords["full"].strip(),
-            "dirty": False, "error": "no suitable tags", "date": None}
-
-
-@register_vcs_handler("git", "pieces_from_vcs")
-def git_pieces_from_vcs(
-    tag_prefix: str,
-    root: str,
-    verbose: bool,
-    runner: Callable = run_command
-) -> Dict[str, Any]:
-    """Get version from 'git describe' in the root of the source tree.
-
-    This only gets called if the git-archive 'subst' keywords were *not*
-    expanded, and _version.py hasn't already been rewritten with a short
-    version string, meaning we're inside a checked out source tree.
-    """
-    GITS = ["git"]
-    if sys.platform == "win32":
-        GITS = ["git.cmd", "git.exe"]
-
-    # GIT_DIR can interfere with correct operation of Versioneer.
-    # It may be intended to be passed to the Versioneer-versioned project,
-    # but that should not change where we get our version from.
-    env = os.environ.copy()
-    env.pop("GIT_DIR", None)
-    runner = functools.partial(runner, env=env)
-
-    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root,
-                   hide_stderr=not verbose)
-    if rc != 0:
-        if verbose:
-            print("Directory %s not under git control" % root)
-        raise NotThisMethod("'git rev-parse --git-dir' returned error")
-
-    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
-    # if there isn't one, this yields HEX[-dirty] (no NUM)
-    describe_out, rc = runner(GITS, [
-        "describe", "--tags", "--dirty", "--always", "--long",
-        "--match", f"{tag_prefix}[[:digit:]]*"
-    ], cwd=root)
-    # --long was added in git-1.5.5
-    if describe_out is None:
-        raise NotThisMethod("'git describe' failed")
-    describe_out = describe_out.strip()
-    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
-    if full_out is None:
-        raise NotThisMethod("'git rev-parse' failed")
-    full_out = full_out.strip()
-
-    pieces: Dict[str, Any] = {}
-    pieces["long"] = full_out
-    pieces["short"] = full_out[:7]  # maybe improved later
-    pieces["error"] = None
-
-    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"],
-                             cwd=root)
-    # --abbrev-ref was added in git-1.6.3
-    if rc != 0 or branch_name is None:
-        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
-    branch_name = branch_name.strip()
-
-    if branch_name == "HEAD":
-        # If we aren't exactly on a branch, pick a branch which represents
-        # the current commit. If all else fails, we are on a branchless
-        # commit.
-        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
-        # --contains was added in git-1.5.4
-        if rc != 0 or branches is None:
-            raise NotThisMethod("'git branch --contains' returned error")
-        branches = branches.split("\n")
-
-        # Remove the first line if we're running detached
-        if "(" in branches[0]:
-            branches.pop(0)
-
-        # Strip off the leading "* " from the list of branches.
-        branches = [branch[2:] for branch in branches]
-        if "master" in branches:
-            branch_name = "master"
-        elif not branches:
-            branch_name = None
-        else:
-            # Pick the first branch that is returned. Good or bad.
-            branch_name = branches[0]
-
-    pieces["branch"] = branch_name
-
-    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
-    # TAG might have hyphens.
-    git_describe = describe_out
-
-    # look for -dirty suffix
-    dirty = git_describe.endswith("-dirty")
-    pieces["dirty"] = dirty
-    if dirty:
-        git_describe = git_describe[:git_describe.rindex("-dirty")]
-
-    # now we have TAG-NUM-gHEX or HEX
-
-    if "-" in git_describe:
-        # TAG-NUM-gHEX
-        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
-        if not mo:
-            # unparsable. Maybe git-describe is misbehaving?
-            pieces["error"] = ("unable to parse git-describe output: '%s'"
-                               % describe_out)
-            return pieces
-
-        # tag
-        full_tag = mo.group(1)
-        if not full_tag.startswith(tag_prefix):
-            if verbose:
-                fmt = "tag '%s' doesn't start with prefix '%s'"
-                print(fmt % (full_tag, tag_prefix))
-            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
-                               % (full_tag, tag_prefix))
-            return pieces
-        pieces["closest-tag"] = full_tag[len(tag_prefix):]
-
-        # distance: number of commits since tag
-        pieces["distance"] = int(mo.group(2))
-
-        # commit: short hex revision ID
-        pieces["short"] = mo.group(3)
-
-    else:
-        # HEX: no tags
-        pieces["closest-tag"] = None
-        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
-        pieces["distance"] = len(out.split())  # total number of commits
-
-    # commit date: see ISO-8601 comment in git_versions_from_keywords()
-    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
-    # Use only the last line.  Previous lines may contain GPG signature
-    # information.
-    date = date.splitlines()[-1]
-    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
-
-    return pieces
-
-
-def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None:
-    """Git-specific installation logic for Versioneer.
-
-    For Git, this means creating/changing .gitattributes to mark _version.py
-    for export-subst keyword substitution.
-    """
-    GITS = ["git"]
-    if sys.platform == "win32":
-        GITS = ["git.cmd", "git.exe"]
-    files = [versionfile_source]
-    if ipy:
-        files.append(ipy)
-    if "VERSIONEER_PEP518" not in globals():
-        try:
-            my_path = __file__
-            if my_path.endswith((".pyc", ".pyo")):
-                my_path = os.path.splitext(my_path)[0] + ".py"
-            versioneer_file = os.path.relpath(my_path)
-        except NameError:
-            versioneer_file = "versioneer.py"
-        files.append(versioneer_file)
-    present = False
-    try:
-        with open(".gitattributes", "r") as fobj:
-            for line in fobj:
-                if line.strip().startswith(versionfile_source):
-                    if "export-subst" in line.strip().split()[1:]:
-                        present = True
-                        break
-    except OSError:
-        pass
-    if not present:
-        with open(".gitattributes", "a+") as fobj:
-            fobj.write(f"{versionfile_source} export-subst\n")
-        files.append(".gitattributes")
-    run_command(GITS, ["add", "--"] + files)
-
-
-def versions_from_parentdir(
-    parentdir_prefix: str,
-    root: str,
-    verbose: bool,
-) -> Dict[str, Any]:
-    """Try to determine the version from the parent directory name.
-
-    Source tarballs conventionally unpack into a directory that includes both
-    the project name and a version string. We will also support searching up
-    two directory levels for an appropriately named parent directory
-    """
-    rootdirs = []
-
-    for _ in range(3):
-        dirname = os.path.basename(root)
-        if dirname.startswith(parentdir_prefix):
-            return {"version": dirname[len(parentdir_prefix):],
-                    "full-revisionid": None,
-                    "dirty": False, "error": None, "date": None}
-        rootdirs.append(root)
-        root = os.path.dirname(root)  # up a level
-
-    if verbose:
-        print("Tried directories %s but none started with prefix %s" %
-              (str(rootdirs), parentdir_prefix))
-    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
-
-
-SHORT_VERSION_PY = """
-# This file was generated by 'versioneer.py' (0.29) from
-# revision-control system data, or from the parent directory name of an
-# unpacked source archive. Distribution tarballs contain a pre-generated copy
-# of this file.
-
-import json
-
-version_json = '''
-%s
-'''  # END VERSION_JSON
-
-
-def get_versions():
-    return json.loads(version_json)
-"""
-
-
-def versions_from_file(filename: str) -> Dict[str, Any]:
-    """Try to determine the version from _version.py if present."""
-    try:
-        with open(filename) as f:
-            contents = f.read()
-    except OSError:
-        raise NotThisMethod("unable to read _version.py")
-    mo = re.search(r"version_json = '''\n(.*)'''  # END VERSION_JSON",
-                   contents, re.M | re.S)
-    if not mo:
-        mo = re.search(r"version_json = '''\r\n(.*)'''  # END VERSION_JSON",
-                       contents, re.M | re.S)
-    if not mo:
-        raise NotThisMethod("no version_json in _version.py")
-    return json.loads(mo.group(1))
-
-
-def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None:
-    """Write the given version number to the given _version.py file."""
-    contents = json.dumps(versions, sort_keys=True,
-                          indent=1, separators=(",", ": "))
-    with open(filename, "w") as f:
-        f.write(SHORT_VERSION_PY % contents)
-
-    print("set %s to '%s'" % (filename, versions["version"]))
-
-
-def plus_or_dot(pieces: Dict[str, Any]) -> str:
-    """Return a + if we don't already have one, else return a ."""
-    if "+" in pieces.get("closest-tag", ""):
-        return "."
-    return "+"
-
-
-def render_pep440(pieces: Dict[str, Any]) -> str:
-    """Build up version string, with post-release "local version identifier".
-
-    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
-    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
-
-    Exceptions:
-    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += plus_or_dot(pieces)
-            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
-                                          pieces["short"])
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def render_pep440_branch(pieces: Dict[str, Any]) -> str:
-    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
-
-    The ".dev0" means not master branch. Note that .dev0 sorts backwards
-    (a feature branch will appear "older" than the master branch).
-
-    Exceptions:
-    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            if pieces["branch"] != "master":
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0"
-        if pieces["branch"] != "master":
-            rendered += ".dev0"
-        rendered += "+untagged.%d.g%s" % (pieces["distance"],
-                                          pieces["short"])
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]:
-    """Split pep440 version string at the post-release segment.
-
-    Returns the release segments before the post-release and the
-    post-release version number (or -1 if no post-release segment is present).
-    """
-    vc = str.split(ver, ".post")
-    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
-
-
-def render_pep440_pre(pieces: Dict[str, Any]) -> str:
-    """TAG[.postN.devDISTANCE] -- No -dirty.
-
-    Exceptions:
-    1: no tags. 0.post0.devDISTANCE
-    """
-    if pieces["closest-tag"]:
-        if pieces["distance"]:
-            # update the post release segment
-            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
-            rendered = tag_version
-            if post_version is not None:
-                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
-            else:
-                rendered += ".post0.dev%d" % (pieces["distance"])
-        else:
-            # no commits, use the tag as the version
-            rendered = pieces["closest-tag"]
-    else:
-        # exception #1
-        rendered = "0.post0.dev%d" % pieces["distance"]
-    return rendered
-
-
-def render_pep440_post(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]+gHEX] .
-
-    The ".dev0" means dirty. Note that .dev0 sorts backwards
-    (a dirty tree will appear "older" than the corresponding clean one),
-    but you shouldn't be releasing software with -dirty anyways.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%d" % pieces["distance"]
-            if pieces["dirty"]:
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "g%s" % pieces["short"]
-    else:
-        # exception #1
-        rendered = "0.post%d" % pieces["distance"]
-        if pieces["dirty"]:
-            rendered += ".dev0"
-        rendered += "+g%s" % pieces["short"]
-    return rendered
-
-
-def render_pep440_post_branch(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
-
-    The ".dev0" means not master branch.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%d" % pieces["distance"]
-            if pieces["branch"] != "master":
-                rendered += ".dev0"
-            rendered += plus_or_dot(pieces)
-            rendered += "g%s" % pieces["short"]
-            if pieces["dirty"]:
-                rendered += ".dirty"
-    else:
-        # exception #1
-        rendered = "0.post%d" % pieces["distance"]
-        if pieces["branch"] != "master":
-            rendered += ".dev0"
-        rendered += "+g%s" % pieces["short"]
-        if pieces["dirty"]:
-            rendered += ".dirty"
-    return rendered
-
-
-def render_pep440_old(pieces: Dict[str, Any]) -> str:
-    """TAG[.postDISTANCE[.dev0]] .
-
-    The ".dev0" means dirty.
-
-    Exceptions:
-    1: no tags. 0.postDISTANCE[.dev0]
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"] or pieces["dirty"]:
-            rendered += ".post%d" % pieces["distance"]
-            if pieces["dirty"]:
-                rendered += ".dev0"
-    else:
-        # exception #1
-        rendered = "0.post%d" % pieces["distance"]
-        if pieces["dirty"]:
-            rendered += ".dev0"
-    return rendered
-
-
-def render_git_describe(pieces: Dict[str, Any]) -> str:
-    """TAG[-DISTANCE-gHEX][-dirty].
-
-    Like 'git describe --tags --dirty --always'.
-
-    Exceptions:
-    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        if pieces["distance"]:
-            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
-    else:
-        # exception #1
-        rendered = pieces["short"]
-    if pieces["dirty"]:
-        rendered += "-dirty"
-    return rendered
-
-
-def render_git_describe_long(pieces: Dict[str, Any]) -> str:
-    """TAG-DISTANCE-gHEX[-dirty].
-
-    Like 'git describe --tags --dirty --always -long'.
-    The distance/hash is unconditional.
-
-    Exceptions:
-    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
-    """
-    if pieces["closest-tag"]:
-        rendered = pieces["closest-tag"]
-        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
-    else:
-        # exception #1
-        rendered = pieces["short"]
-    if pieces["dirty"]:
-        rendered += "-dirty"
-    return rendered
-
-
-def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]:
-    """Render the given version pieces into the requested style."""
-    if pieces["error"]:
-        return {"version": "unknown",
-                "full-revisionid": pieces.get("long"),
-                "dirty": None,
-                "error": pieces["error"],
-                "date": None}
-
-    if not style or style == "default":
-        style = "pep440"  # the default
-
-    if style == "pep440":
-        rendered = render_pep440(pieces)
-    elif style == "pep440-branch":
-        rendered = render_pep440_branch(pieces)
-    elif style == "pep440-pre":
-        rendered = render_pep440_pre(pieces)
-    elif style == "pep440-post":
-        rendered = render_pep440_post(pieces)
-    elif style == "pep440-post-branch":
-        rendered = render_pep440_post_branch(pieces)
-    elif style == "pep440-old":
-        rendered = render_pep440_old(pieces)
-    elif style == "git-describe":
-        rendered = render_git_describe(pieces)
-    elif style == "git-describe-long":
-        rendered = render_git_describe_long(pieces)
-    else:
-        raise ValueError("unknown style '%s'" % style)
-
-    return {"version": rendered, "full-revisionid": pieces["long"],
-            "dirty": pieces["dirty"], "error": None,
-            "date": pieces.get("date")}
-
-
-class VersioneerBadRootError(Exception):
-    """The project root directory is unknown or missing key files."""
-
-
-def get_versions(verbose: bool = False) -> Dict[str, Any]:
-    """Get the project version from whatever source is available.
-
-    Returns dict with two keys: 'version' and 'full'.
-    """
-    if "versioneer" in sys.modules:
-        # see the discussion in cmdclass.py:get_cmdclass()
-        del sys.modules["versioneer"]
-
-    root = get_root()
-    cfg = get_config_from_root(root)
-
-    assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg"
-    handlers = HANDLERS.get(cfg.VCS)
-    assert handlers, "unrecognized VCS '%s'" % cfg.VCS
-    verbose = verbose or bool(cfg.verbose)  # `bool()` used to avoid `None`
-    assert cfg.versionfile_source is not None, \
-        "please set versioneer.versionfile_source"
-    assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix"
-
-    versionfile_abs = os.path.join(root, cfg.versionfile_source)
-
-    # extract version from first of: _version.py, VCS command (e.g. 'git
-    # describe'), parentdir. This is meant to work for developers using a
-    # source checkout, for users of a tarball created by 'setup.py sdist',
-    # and for users of a tarball/zipball created by 'git archive' or github's
-    # download-from-tag feature or the equivalent in other VCSes.
-
-    get_keywords_f = handlers.get("get_keywords")
-    from_keywords_f = handlers.get("keywords")
-    if get_keywords_f and from_keywords_f:
-        try:
-            keywords = get_keywords_f(versionfile_abs)
-            ver = from_keywords_f(keywords, cfg.tag_prefix, verbose)
-            if verbose:
-                print("got version from expanded keyword %s" % ver)
-            return ver
-        except NotThisMethod:
-            pass
-
-    try:
-        ver = versions_from_file(versionfile_abs)
-        if verbose:
-            print("got version from file %s %s" % (versionfile_abs, ver))
-        return ver
-    except NotThisMethod:
-        pass
-
-    from_vcs_f = handlers.get("pieces_from_vcs")
-    if from_vcs_f:
-        try:
-            pieces = from_vcs_f(cfg.tag_prefix, root, verbose)
-            ver = render(pieces, cfg.style)
-            if verbose:
-                print("got version from VCS %s" % ver)
-            return ver
-        except NotThisMethod:
-            pass
-
-    try:
-        if cfg.parentdir_prefix:
-            ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
-            if verbose:
-                print("got version from parentdir %s" % ver)
-            return ver
-    except NotThisMethod:
-        pass
-
-    if verbose:
-        print("unable to compute version")
-
-    return {"version": "0+unknown", "full-revisionid": None,
-            "dirty": None, "error": "unable to compute version",
-            "date": None}
-
-
-def get_version() -> str:
-    """Get the short version string for this project."""
-    return get_versions()["version"]
-
-
-def get_cmdclass(cmdclass: Optional[Dict[str, Any]] = None):
-    """Get the custom setuptools subclasses used by Versioneer.
-
-    If the package uses a different cmdclass (e.g. one from numpy), it
-    should be provide as an argument.
-    """
-    if "versioneer" in sys.modules:
-        del sys.modules["versioneer"]
-        # this fixes the "python setup.py develop" case (also 'install' and
-        # 'easy_install .'), in which subdependencies of the main project are
-        # built (using setup.py bdist_egg) in the same python process. Assume
-        # a main project A and a dependency B, which use different versions
-        # of Versioneer. A's setup.py imports A's Versioneer, leaving it in
-        # sys.modules by the time B's setup.py is executed, causing B to run
-        # with the wrong versioneer. Setuptools wraps the sub-dep builds in a
-        # sandbox that restores sys.modules to it's pre-build state, so the
-        # parent is protected against the child's "import versioneer". By
-        # removing ourselves from sys.modules here, before the child build
-        # happens, we protect the child from the parent's versioneer too.
-        # Also see https://github.com/python-versioneer/python-versioneer/issues/52
-
-    cmds = {} if cmdclass is None else cmdclass.copy()
-
-    # we add "version" to setuptools
-    from setuptools import Command
-
-    class cmd_version(Command):
-        description = "report generated version string"
-        user_options: List[Tuple[str, str, str]] = []
-        boolean_options: List[str] = []
-
-        def initialize_options(self) -> None:
-            pass
-
-        def finalize_options(self) -> None:
-            pass
-
-        def run(self) -> None:
-            vers = get_versions(verbose=True)
-            print("Version: %s" % vers["version"])
-            print(" full-revisionid: %s" % vers.get("full-revisionid"))
-            print(" dirty: %s" % vers.get("dirty"))
-            print(" date: %s" % vers.get("date"))
-            if vers["error"]:
-                print(" error: %s" % vers["error"])
-    cmds["version"] = cmd_version
-
-    # we override "build_py" in setuptools
-    #
-    # most invocation pathways end up running build_py:
-    #  distutils/build -> build_py
-    #  distutils/install -> distutils/build ->..
-    #  setuptools/bdist_wheel -> distutils/install ->..
-    #  setuptools/bdist_egg -> distutils/install_lib -> build_py
-    #  setuptools/install -> bdist_egg ->..
-    #  setuptools/develop -> ?
-    #  pip install:
-    #   copies source tree to a tempdir before running egg_info/etc
-    #   if .git isn't copied too, 'git describe' will fail
-    #   then does setup.py bdist_wheel, or sometimes setup.py install
-    #  setup.py egg_info -> ?
-
-    # pip install -e . and setuptool/editable_wheel will invoke build_py
-    # but the build_py command is not expected to copy any files.
-
-    # we override different "build_py" commands for both environments
-    if 'build_py' in cmds:
-        _build_py: Any = cmds['build_py']
-    else:
-        from setuptools.command.build_py import build_py as _build_py
-
-    class cmd_build_py(_build_py):
-        def run(self) -> None:
-            root = get_root()
-            cfg = get_config_from_root(root)
-            versions = get_versions()
-            _build_py.run(self)
-            if getattr(self, "editable_mode", False):
-                # During editable installs `.py` and data files are
-                # not copied to build_lib
-                return
-            # now locate _version.py in the new build/ directory and replace
-            # it with an updated value
-            if cfg.versionfile_build:
-                target_versionfile = os.path.join(self.build_lib,
-                                                  cfg.versionfile_build)
-                print("UPDATING %s" % target_versionfile)
-                write_to_version_file(target_versionfile, versions)
-    cmds["build_py"] = cmd_build_py
-
-    if 'build_ext' in cmds:
-        _build_ext: Any = cmds['build_ext']
-    else:
-        from setuptools.command.build_ext import build_ext as _build_ext
-
-    class cmd_build_ext(_build_ext):
-        def run(self) -> None:
-            root = get_root()
-            cfg = get_config_from_root(root)
-            versions = get_versions()
-            _build_ext.run(self)
-            if self.inplace:
-                # build_ext --inplace will only build extensions in
-                # build/lib<..> dir with no _version.py to write to.
-                # As in place builds will already have a _version.py
-                # in the module dir, we do not need to write one.
-                return
-            # now locate _version.py in the new build/ directory and replace
-            # it with an updated value
-            if not cfg.versionfile_build:
-                return
-            target_versionfile = os.path.join(self.build_lib,
-                                              cfg.versionfile_build)
-            if not os.path.exists(target_versionfile):
-                print(f"Warning: {target_versionfile} does not exist, skipping "
-                      "version update. This can happen if you are running build_ext "
-                      "without first running build_py.")
-                return
-            print("UPDATING %s" % target_versionfile)
-            write_to_version_file(target_versionfile, versions)
-    cmds["build_ext"] = cmd_build_ext
-
-    if "cx_Freeze" in sys.modules:  # cx_freeze enabled?
-        from cx_Freeze.dist import build_exe as _build_exe  # type: ignore
-        # nczeczulin reports that py2exe won't like the pep440-style string
-        # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g.
-        # setup(console=[{
-        #   "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION
-        #   "product_version": versioneer.get_version(),
-        #   ...
-
-        class cmd_build_exe(_build_exe):
-            def run(self) -> None:
-                root = get_root()
-                cfg = get_config_from_root(root)
-                versions = get_versions()
-                target_versionfile = cfg.versionfile_source
-                print("UPDATING %s" % target_versionfile)
-                write_to_version_file(target_versionfile, versions)
-
-                _build_exe.run(self)
-                os.unlink(target_versionfile)
-                with open(cfg.versionfile_source, "w") as f:
-                    LONG = LONG_VERSION_PY[cfg.VCS]
-                    f.write(LONG %
-                            {"DOLLAR": "$",
-                             "STYLE": cfg.style,
-                             "TAG_PREFIX": cfg.tag_prefix,
-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
-                             })
-        cmds["build_exe"] = cmd_build_exe
-        del cmds["build_py"]
-
-    if 'py2exe' in sys.modules:  # py2exe enabled?
-        try:
-            from py2exe.setuptools_buildexe import py2exe as _py2exe  # type: ignore
-        except ImportError:
-            from py2exe.distutils_buildexe import py2exe as _py2exe  # type: ignore
-
-        class cmd_py2exe(_py2exe):
-            def run(self) -> None:
-                root = get_root()
-                cfg = get_config_from_root(root)
-                versions = get_versions()
-                target_versionfile = cfg.versionfile_source
-                print("UPDATING %s" % target_versionfile)
-                write_to_version_file(target_versionfile, versions)
-
-                _py2exe.run(self)
-                os.unlink(target_versionfile)
-                with open(cfg.versionfile_source, "w") as f:
-                    LONG = LONG_VERSION_PY[cfg.VCS]
-                    f.write(LONG %
-                            {"DOLLAR": "$",
-                             "STYLE": cfg.style,
-                             "TAG_PREFIX": cfg.tag_prefix,
-                             "PARENTDIR_PREFIX": cfg.parentdir_prefix,
-                             "VERSIONFILE_SOURCE": cfg.versionfile_source,
-                             })
-        cmds["py2exe"] = cmd_py2exe
-
-    # sdist farms its file list building out to egg_info
-    if 'egg_info' in cmds:
-        _egg_info: Any = cmds['egg_info']
-    else:
-        from setuptools.command.egg_info import egg_info as _egg_info
-
-    class cmd_egg_info(_egg_info):
-        def find_sources(self) -> None:
-            # egg_info.find_sources builds the manifest list and writes it
-            # in one shot
-            super().find_sources()
-
-            # Modify the filelist and normalize it
-            root = get_root()
-            cfg = get_config_from_root(root)
-            self.filelist.append('versioneer.py')
-            if cfg.versionfile_source:
-                # There are rare cases where versionfile_source might not be
-                # included by default, so we must be explicit
-                self.filelist.append(cfg.versionfile_source)
-            self.filelist.sort()
-            self.filelist.remove_duplicates()
-
-            # The write method is hidden in the manifest_maker instance that
-            # generated the filelist and was thrown away
-            # We will instead replicate their final normalization (to unicode,
-            # and POSIX-style paths)
-            from setuptools import unicode_utils
-            normalized = [unicode_utils.filesys_decode(f).replace(os.sep, '/')
-                          for f in self.filelist.files]
-
-            manifest_filename = os.path.join(self.egg_info, 'SOURCES.txt')
-            with open(manifest_filename, 'w') as fobj:
-                fobj.write('\n'.join(normalized))
-
-    cmds['egg_info'] = cmd_egg_info
-
-    # we override different "sdist" commands for both environments
-    if 'sdist' in cmds:
-        _sdist: Any = cmds['sdist']
-    else:
-        from setuptools.command.sdist import sdist as _sdist
-
-    class cmd_sdist(_sdist):
-        def run(self) -> None:
-            versions = get_versions()
-            self._versioneer_generated_versions = versions
-            # unless we update this, the command will keep using the old
-            # version
-            self.distribution.metadata.version = versions["version"]
-            return _sdist.run(self)
-
-        def make_release_tree(self, base_dir: str, files: List[str]) -> None:
-            root = get_root()
-            cfg = get_config_from_root(root)
-            _sdist.make_release_tree(self, base_dir, files)
-            # now locate _version.py in the new base_dir directory
-            # (remembering that it may be a hardlink) and replace it with an
-            # updated value
-            target_versionfile = os.path.join(base_dir, cfg.versionfile_source)
-            print("UPDATING %s" % target_versionfile)
-            write_to_version_file(target_versionfile,
-                                  self._versioneer_generated_versions)
-    cmds["sdist"] = cmd_sdist
-
-    return cmds
-
-
-CONFIG_ERROR = """
-setup.cfg is missing the necessary Versioneer configuration. You need
-a section like:
-
- [versioneer]
- VCS = git
- style = pep440
- versionfile_source = src/myproject/_version.py
- versionfile_build = myproject/_version.py
- tag_prefix =
- parentdir_prefix = myproject-
-
-You will also need to edit your setup.py to use the results:
-
- import versioneer
- setup(version=versioneer.get_version(),
-       cmdclass=versioneer.get_cmdclass(), ...)
-
-Please read the docstring in ./versioneer.py for configuration instructions,
-edit setup.cfg, and re-run the installer or 'python versioneer.py setup'.
-"""
-
-SAMPLE_CONFIG = """
-# See the docstring in versioneer.py for instructions. Note that you must
-# re-run 'versioneer.py setup' after changing this section, and commit the
-# resulting files.
-
-[versioneer]
-#VCS = git
-#style = pep440
-#versionfile_source =
-#versionfile_build =
-#tag_prefix =
-#parentdir_prefix =
-
-"""
-
-OLD_SNIPPET = """
-from ._version import get_versions
-__version__ = get_versions()['version']
-del get_versions
-"""
-
-INIT_PY_SNIPPET = """
-from . import {0}
-__version__ = {0}.get_versions()['version']
-"""
-
-
-def do_setup() -> int:
-    """Do main VCS-independent setup function for installing Versioneer."""
-    root = get_root()
-    try:
-        cfg = get_config_from_root(root)
-    except (OSError, configparser.NoSectionError,
-            configparser.NoOptionError) as e:
-        if isinstance(e, (OSError, configparser.NoSectionError)):
-            print("Adding sample versioneer config to setup.cfg",
-                  file=sys.stderr)
-            with open(os.path.join(root, "setup.cfg"), "a") as f:
-                f.write(SAMPLE_CONFIG)
-        print(CONFIG_ERROR, file=sys.stderr)
-        return 1
-
-    print(" creating %s" % cfg.versionfile_source)
-    with open(cfg.versionfile_source, "w") as f:
-        LONG = LONG_VERSION_PY[cfg.VCS]
-        f.write(LONG % {"DOLLAR": "$",
-                        "STYLE": cfg.style,
-                        "TAG_PREFIX": cfg.tag_prefix,
-                        "PARENTDIR_PREFIX": cfg.parentdir_prefix,
-                        "VERSIONFILE_SOURCE": cfg.versionfile_source,
-                        })
-
-    ipy = os.path.join(os.path.dirname(cfg.versionfile_source),
-                       "__init__.py")
-    maybe_ipy: Optional[str] = ipy
-    if os.path.exists(ipy):
-        try:
-            with open(ipy, "r") as f:
-                old = f.read()
-        except OSError:
-            old = ""
-        module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0]
-        snippet = INIT_PY_SNIPPET.format(module)
-        if OLD_SNIPPET in old:
-            print(" replacing boilerplate in %s" % ipy)
-            with open(ipy, "w") as f:
-                f.write(old.replace(OLD_SNIPPET, snippet))
-        elif snippet not in old:
-            print(" appending to %s" % ipy)
-            with open(ipy, "a") as f:
-                f.write(snippet)
-        else:
-            print(" %s unmodified" % ipy)
-    else:
-        print(" %s doesn't exist, ok" % ipy)
-        maybe_ipy = None
-
-    # Make VCS-specific changes. For git, this means creating/changing
-    # .gitattributes to mark _version.py for export-subst keyword
-    # substitution.
-    do_vcs_install(cfg.versionfile_source, maybe_ipy)
-    return 0
-
-
-def scan_setup_py() -> int:
-    """Validate the contents of setup.py against Versioneer's expectations."""
-    found = set()
-    setters = False
-    errors = 0
-    with open("setup.py", "r") as f:
-        for line in f.readlines():
-            if "import versioneer" in line:
-                found.add("import")
-            if "versioneer.get_cmdclass()" in line:
-                found.add("cmdclass")
-            if "versioneer.get_version()" in line:
-                found.add("get_version")
-            if "versioneer.VCS" in line:
-                setters = True
-            if "versioneer.versionfile_source" in line:
-                setters = True
-    if len(found) != 3:
-        print("")
-        print("Your setup.py appears to be missing some important items")
-        print("(but I might be wrong). Please make sure it has something")
-        print("roughly like the following:")
-        print("")
-        print(" import versioneer")
-        print(" setup( version=versioneer.get_version(),")
-        print("        cmdclass=versioneer.get_cmdclass(),  ...)")
-        print("")
-        errors += 1
-    if setters:
-        print("You should remove lines like 'versioneer.VCS = ' and")
-        print("'versioneer.versionfile_source = ' . This configuration")
-        print("now lives in setup.cfg, and should be removed from setup.py")
-        print("")
-        errors += 1
-    return errors
-
-
-def setup_command() -> NoReturn:
-    """Set up Versioneer and exit with appropriate error code."""
-    errors = do_setup()
-    errors += scan_setup_py()
-    sys.exit(1 if errors else 0)
-
-
-if __name__ == "__main__":
-    cmd = sys.argv[1]
-    if cmd == "setup":
-        setup_command()