Repository: pasqal-io/Pulser (language: python)

Issue:
Splitting the `pulser.simulation` module
## Objectives:
- Have all the features of the `pulser.simulation` module in a separate distribution, thus allowing for a leaner `pulser-core` distribution that's entirely focused on the sequence design aspect.
- Keep existing code working, at least until the next major release (which will be 1.0.0)
- Minimize the need for code adaptations on the user side as much as possible
- `pip install pulser` would still install everything, but now there is the option to `pip install pulser-core`(or similar) for situations where the simulation package is not needed.

## Identified potential solutions

### 1. Namespace packages

[Namespace packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/) allow for a single package to be split into multiple sub-packages, which can be distributed, installed and used independently.

The advantage of this approach is that we could make `pulser` a namespace package and `pulser-simulation` a subpackage, which would allow us to keep the `from pulser.simulation import ...` imports unchanged. It would also mean that all the other modules would be moved to a `pulser-core` subpackage. We would thus move from this:

```
pulser/
    __init__.py
    setup.py
    simulation/
        __init__.py
        ...
    devices/
        __init__.py
        ...
    sequence.py
    [all the other pulser modules]
```

to this:

```
pulser-simulation/
    setup.py
    pulser/
        simulation/
            __init__.py
            ...

pulser-core/
    setup.py
    pulser/
        devices/
            __init__.py
            ...
        sequence.py
        [all the other pulser modules]
```

When `pulser-simulation` and `pulser-core` are installed, their contents would then be located under the `pulser` namespace package.

As far as I could tell, not being able to have an `__init__.py` means that we have to drop the direct imports from `pulser` (like `from pulser import Sequence`), which would defeat the purpose of keeping existing code working. I haven't experiment with this yet, so perhaps there is a way, but if there is it is obscure. **If there is indeed no way to do this, I would say this makes this approach unviable.**

### 2. Make a separate `pulser_simulation` package

This is a more straightforward solution, which would essentially just extract the simulation module and keep the rest the same. In this case, the file structure would look something like this.

```
pulser-simulation/
    setup.py   # pulser_simulation package setup, with pulser as a dependency (distribution: 'pulser-simulation')
    pulser_simulation/
        __init__.py
        simulation.py
        simresults.py
        ...         

pulser-core/
    setup.py   # pulser package setup (distribution: 'pulser-core')
    pulser/
        devices/
            __init__.py
            ...
        sequence.py
        [all the other pulser modules]
setup.py  # 'pulser' distribution, just installs 'pulser-core' and 'pulser-simulation'
```

As it is, this approach will have the inconvenience of requiring that all imports of `pulser.simulation` be changed to `pulser_simulation`. I think a workaround here is to keep an empty `simulation` module inside `pulser` with only an `__init__.py` containing code that checks if `pulser_simulation` is installed and imports the relevant classes from it (i.e. `Simulation` and `SimConfig`). I think this would be fine for now, I'm not so sure it's something we would like to keep forever or deprecate at some point.
Splitting the `pulser.simulation` module
## Objectives:
- Have all the features of the `pulser.simulation` module in a separate distribution, thus allowing for a leaner `pulser-core` distribution that's entirely focused on the sequence design aspect.
- Keep existing code working, at least until the next major release (which will be 1.0.0)
- Minimize the need for code adaptations on the user side as much as possible
- `pip install pulser` would still install everything, but now there is the option to `pip install pulser-core`(or similar) for situations where the simulation package is not needed.

## Identified potential solutions

### 1. Namespace packages

[Namespace packages](https://packaging.python.org/en/latest/guides/packaging-namespace-packages/) allow for a single package to be split into multiple sub-packages, which can be distributed, installed and used independently.

The advantage of this approach is that we could make `pulser` a namespace package and `pulser-simulation` a subpackage, which would allow us to keep the `from pulser.simulation import ...` imports unchanged. It would also mean that all the other modules would be moved to a `pulser-core` subpackage. We would thus move from this:

```
pulser/
    __init__.py
    setup.py
    simulation/
        __init__.py
        ...
    devices/
        __init__.py
        ...
    sequence.py
    [all the other pulser modules]
```

to this:

```
pulser-simulation/
    setup.py
    pulser/
        simulation/
            __init__.py
            ...

pulser-core/
    setup.py
    pulser/
        devices/
            __init__.py
            ...
        sequence.py
        [all the other pulser modules]
```

When `pulser-simulation` and `pulser-core` are installed, their contents would then be located under the `pulser` namespace package.

As far as I could tell, not being able to have an `__init__.py` means that we have to drop the direct imports from `pulser` (like `from pulser import Sequence`), which would defeat the purpose of keeping existing code working. I haven't experiment with this yet, so perhaps there is a way, but if there is it is obscure. **If there is indeed no way to do this, I would say this makes this approach unviable.**

### 2. Make a separate `pulser_simulation` package

This is a more straightforward solution, which would essentially just extract the simulation module and keep the rest the same. In this case, the file structure would look something like this.

```
pulser-simulation/
    setup.py   # pulser_simulation package setup, with pulser as a dependency (distribution: 'pulser-simulation')
    pulser_simulation/
        __init__.py
        simulation.py
        simresults.py
        ...         

pulser-core/
    setup.py   # pulser package setup (distribution: 'pulser-core')
    pulser/
        devices/
            __init__.py
            ...
        sequence.py
        [all the other pulser modules]
setup.py  # 'pulser' distribution, just installs 'pulser-core' and 'pulser-simulation'
```

As it is, this approach will have the inconvenience of requiring that all imports of `pulser.simulation` be changed to `pulser_simulation`. I think a workaround here is to keep an empty `simulation` module inside `pulser` with only an `__init__.py` containing code that checks if `pulser_simulation` is installed and imports the relevant classes from it (i.e. `Simulation` and `SimConfig`). I think this would be fine for now, I'm not so sure it's something we would like to keep forever or deprecate at some point.


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
index 520d0bb22..5fe460104 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -7,6 +7,14 @@ exclude_lines =
     # Don't test pass statements
     pass
 
-omit = pulser/tests/*
+omit =
+    tests/*
+    setup.py
+
 
 show_missing = True
+
+[run]
+source =
+    pulser-core/pulser/
+    pulser-simulation/pulser_simulation/
diff --git a/.flake8 b/.flake8
index 29fee19f4..c1ecc28d2 100644
--- a/.flake8
+++ b/.flake8
@@ -10,6 +10,6 @@ per-file-ignores =
   # D100 Missing docstring in public module
   # D103 Missing docstring in public function
   # F401 Module imported but unused
-  pulser/tests/*: D100, D103
+  tests/*: D100, D103
   __init__.py: F401
   setup.py: D100
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 073b63888..d191c3137 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,7 @@ jobs:
       - name: Install Python dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -e .
+          pip install -e ./pulser-core -e ./pulser-simulation
           pip install -r requirements.txt
       - name: Lint with flake8
         run: flake8
@@ -55,7 +55,7 @@ jobs:
       - name: Install Python dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -e .
+          pip install -e ./pulser-core -e ./pulser-simulation
           pip install -r requirements.txt
       - name: Check import sorting with isort
         run: isort --check-only --diff .
@@ -71,7 +71,7 @@ jobs:
       - name: Install Python dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -e .
+          pip install -e ./pulser-core -e ./pulser-simulation
           pip install -r requirements.txt
       - name: Type check with mypy
         run: mypy
@@ -91,7 +91,7 @@ jobs:
       - name: Install Python dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -e .
+          pip install -e ./pulser-core -e ./pulser-simulation
           pip install -r requirements.txt
       - name: Run the unit tests & generate coverage report
-        run: pytest --cov --cov-fail-under=100 pulser
+        run: pytest --cov --cov-fail-under=100
diff --git a/.gitignore b/.gitignore
index fd086db79..6238e047a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ docs/build/
 dist/
 env*
 pulser.egg-info/
+pulser_simulation.egg-info/
+pulser_core.egg-info/
diff --git a/.mypy.ini b/.mypy.ini
index 57d21eb79..e65456272 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -1,5 +1,5 @@
 [mypy]
-files = pulser
+files = pulser-core/pulser, pulser-simulation/pulser_simulation, tests
 python_version = 3.8
 warn_return_any = True
 warn_redundant_casts = True
@@ -11,5 +11,5 @@ disallow_untyped_defs = True
 follow_imports = silent
 ignore_missing_imports = true
 
-[mypy-pulser.tests.*]
+[mypy-tests.*]
 disallow_untyped_defs = False
diff --git a/.readthedocs.yml b/.readthedocs.yml
index d175a7f26..69ee988bc 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -15,5 +15,5 @@ python:
    install:
      - requirements: docs/requirements.txt
      - requirements: requirements.txt
-     - method: setuptools
-       path: .
+     - requirements: pulser-core/rtd_requirements.txt
+     - requirements: pulser-simulation/rtd_requirements.txt
diff --git a/MANIFEST.in b/MANIFEST.in
index d0bd766ee..b217d8188 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
 include README.md
 include requirements.txt
 include LICENSE
-include pulser/devices/interaction_coefficients/C6_coeffs.json
+include VERSION
diff --git a/README.md b/README.md
index eb6cceed2..46b6846f7 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ and also allows the creation of **analog** quantum simulations, outside of the
 scope of traditional quantum circuit approaches. Whatever the type of experiment
 or paradigm, if it can be done on the device, it can be done with Pulser.
 
-Additionally, Pulser features built-in tools for classical simulation (using [QuTiP][qutip] libraries) to aid in the development and testing of new pulse sequences.
+Additionally, the ``pulser_simulation`` extension provides tools for classical simulation (using [QuTiP][qutip] libraries) to aid in the development and testing of new pulse sequences.
 
 For a comprehensive overview of Pulser, check out [Pulser's arXiv preprint](https://arxiv.org/abs/2104.15044).
 
@@ -27,11 +27,21 @@ To install the latest release of ``pulser``, have Python 3.7.0 or higher install
 pip install pulser
 ```
 
+The standard ``pulser`` distribution will install the core ``pulser`` package
+and the ``pulser_simulation`` extension package, which is required if you want
+to access the emulation features.
+
+If you wish to install only the core ``pulser`` features, you can instead run:
+
+```bash
+pip install pulser-core
+```
+
 If you wish to **install the development version of Pulser from source** instead, do the following from within this repository after cloning it:
 
 ```bash
 git checkout develop
-pip install -e .
+pip install -e ./pulser-core -e ./pulser-simulation
 ```
 
 Bear in mind that this installation will track the contents of your local
@@ -49,7 +59,7 @@ pip install -r requirements.txt
 Then, you can do the following to run the test suite and report test coverage:
 
 ```bash
-pytest --cov pulser
+pytest --cov
 ```
 
 ## Contributing
diff --git a/VERSION.txt b/VERSION.txt
new file mode 100644
index 000000000..a5156d36a
--- /dev/null
+++ b/VERSION.txt
@@ -0,0 +1,1 @@
+0.5.1.dev
diff --git a/docs/source/apidoc/emulation.rst b/docs/source/apidoc/emulation.rst
index 9883262ff..0d5a5a9de 100644
--- a/docs/source/apidoc/emulation.rst
+++ b/docs/source/apidoc/emulation.rst
@@ -2,16 +2,19 @@
 Classical Simulation
 *********************
 
+Since version 0.6.0, all simulation classes (previously under the ``pulser.simulation`` module)
+are in the ``pulser-simulation`` extension and should be imported from ``pulser_simulation``.
+
 Simulation
 ----------------------
 
-.. automodule:: pulser.simulation.simulation
+.. automodule:: pulser_simulation.simulation
    :members:
 
 SimConfig
 ----------------------
 
-.. automodule:: pulser.simulation.simconfig
+.. automodule:: pulser_simulation.simconfig
    :members:
 
 Simulation Results
@@ -29,7 +32,7 @@ within them.
 CoherentResults
 ^^^^^^^^^^^^^^^^
 
-.. autoclass:: pulser.simulation.simresults.CoherentResults
+.. autoclass:: pulser_simulation.simresults.CoherentResults
    :members:
    :inherited-members:
 
@@ -37,6 +40,6 @@ CoherentResults
 NoisyResults
 ^^^^^^^^^^^^^^^^
 
-.. autoclass:: pulser.simulation.simresults.NoisyResults
+.. autoclass:: pulser_simulation.simresults.NoisyResults
   :members:
   :inherited-members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
index cbaa73908..7dd7ee6df 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -13,15 +13,15 @@
 import os
 import sys
 
-sys.path.insert(0, os.path.abspath("../../pulser"))
+sys.path.insert(0, os.path.abspath("../../"))
 
-__version__ = ""
-exec(open("../../pulser/_version.py").read())
+with open("../../VERSION.txt", "r") as f:
+    __version__ = f.read().strip()
 
 # -- Project information -----------------------------------------------------
 
 project = "Pulser"
-copyright = "2020, Pulser Development Team"
+copyright = "2022, Pulser Development Team"
 author = "Pulser Development Team"
 
 # The full version, including alpha/beta/rc tags
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 12d7e89b2..17c74e96e 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -8,6 +8,14 @@ installed, then use ``pip``: ::
 
   pip install pulser
 
+The standard ``pulser`` distribution will install the core ``pulser`` package
+and the ``pulser_simulation`` extension package, which is required if you want
+to access the :doc:`apidoc/emulation` features.
+
+If you wish to install only the core ``pulser`` features, you can instead run: ::
+
+  pip install pulser-core
+
 
 Latest version
 ---------------
@@ -18,7 +26,7 @@ the ``develop`` branch - which holds the latest (unstable) version of Pulser -
 and install from source by running: ::
 
   git checkout develop
-  pip install -e .
+  pip install -e ./pulser-core -e ./pulser-simulation
 
 Bear in mind that your installation will track the contents of your local
 Pulser repository folder, so if you checkout a different branch (e.g. ``master``),
diff --git a/docs/source/intro_rydberg_blockade.ipynb b/docs/source/intro_rydberg_blockade.ipynb
index ba7eb8459..38f3fb929 100644
--- a/docs/source/intro_rydberg_blockade.ipynb
+++ b/docs/source/intro_rydberg_blockade.ipynb
@@ -196,7 +196,7 @@
    ],
    "source": [
     "import matplotlib.pyplot as plt\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "\n",
     "data = []\n",
     "distances = np.linspace(6.5, 14, 7)\n",
diff --git a/pulser-core/LICENSE b/pulser-core/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/pulser-core/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/pulser-core/MANIFEST.in b/pulser-core/MANIFEST.in
new file mode 100644
index 000000000..c6be02f03
--- /dev/null
+++ b/pulser-core/MANIFEST.in
@@ -0,0 +1,3 @@
+include README.md
+include LICENSE
+include pulser/devices/interaction_coefficients/C6_coeffs.json
diff --git a/pulser-core/README.md b/pulser-core/README.md
new file mode 100644
index 000000000..1f574875e
--- /dev/null
+++ b/pulser-core/README.md
@@ -0,0 +1,21 @@
+# pulser-core
+
+Pulser is a framework for composing, simulating and executing **pulse** sequences for neutral-atom quantum devices.
+
+This is the `pulser-core` package, which provides the core functionalities needed to create sequences
+with `pulser`.
+
+## Installation
+
+The standard Pulser installation,
+
+```bash
+pip install pulser
+```
+
+will automatically install `pulser-core`, along with the extension packages.
+If you wish to install `pulser-core` on its own, you can instead run
+
+```bash
+pip install pulser-core
+```
diff --git a/pulser/__init__.py b/pulser-core/pulser/__init__.py
similarity index 95%
rename from pulser/__init__.py
rename to pulser-core/pulser/__init__.py
index eb8ea3b5a..128bde91a 100644
--- a/pulser/__init__.py
+++ b/pulser-core/pulser/__init__.py
@@ -18,4 +18,3 @@
 from pulser.pulse import Pulse
 from pulser.register import Register, Register3D
 from pulser.sequence import Sequence
-from pulser.simulation import Simulation
diff --git a/pulser/_seq_drawer.py b/pulser-core/pulser/_seq_drawer.py
similarity index 100%
rename from pulser/_seq_drawer.py
rename to pulser-core/pulser/_seq_drawer.py
diff --git a/pulser/_version.py b/pulser-core/pulser/_version.py
similarity index 72%
rename from pulser/_version.py
rename to pulser-core/pulser/_version.py
index 292c3491b..0f0d9bfe3 100644
--- a/pulser/_version.py
+++ b/pulser-core/pulser/_version.py
@@ -11,5 +11,10 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+from pathlib import PurePath
 
-__version__ = "0.5.1.dev"
+# Sets the version to the same as 'pulser'.
+version_file_path = PurePath(__file__).parent.parent.parent / "VERSION.txt"
+
+with open(version_file_path, "r") as f:
+    __version__ = f.read().strip()
diff --git a/pulser/channels.py b/pulser-core/pulser/channels.py
similarity index 100%
rename from pulser/channels.py
rename to pulser-core/pulser/channels.py
diff --git a/pulser/devices/__init__.py b/pulser-core/pulser/devices/__init__.py
similarity index 100%
rename from pulser/devices/__init__.py
rename to pulser-core/pulser/devices/__init__.py
diff --git a/pulser/devices/_device_datacls.py b/pulser-core/pulser/devices/_device_datacls.py
similarity index 100%
rename from pulser/devices/_device_datacls.py
rename to pulser-core/pulser/devices/_device_datacls.py
diff --git a/pulser/devices/_devices.py b/pulser-core/pulser/devices/_devices.py
similarity index 100%
rename from pulser/devices/_devices.py
rename to pulser-core/pulser/devices/_devices.py
diff --git a/pulser/devices/_mock_device.py b/pulser-core/pulser/devices/_mock_device.py
similarity index 100%
rename from pulser/devices/_mock_device.py
rename to pulser-core/pulser/devices/_mock_device.py
diff --git a/pulser/devices/interaction_coefficients/C6_coeffs.json b/pulser-core/pulser/devices/interaction_coefficients/C6_coeffs.json
similarity index 100%
rename from pulser/devices/interaction_coefficients/C6_coeffs.json
rename to pulser-core/pulser/devices/interaction_coefficients/C6_coeffs.json
diff --git a/pulser/devices/interaction_coefficients/__init__.py b/pulser-core/pulser/devices/interaction_coefficients/__init__.py
similarity index 100%
rename from pulser/devices/interaction_coefficients/__init__.py
rename to pulser-core/pulser/devices/interaction_coefficients/__init__.py
diff --git a/pulser/json/__init__.py b/pulser-core/pulser/json/__init__.py
similarity index 100%
rename from pulser/json/__init__.py
rename to pulser-core/pulser/json/__init__.py
diff --git a/pulser/json/coders.py b/pulser-core/pulser/json/coders.py
similarity index 100%
rename from pulser/json/coders.py
rename to pulser-core/pulser/json/coders.py
diff --git a/pulser/json/supported.py b/pulser-core/pulser/json/supported.py
similarity index 100%
rename from pulser/json/supported.py
rename to pulser-core/pulser/json/supported.py
diff --git a/pulser/json/utils.py b/pulser-core/pulser/json/utils.py
similarity index 100%
rename from pulser/json/utils.py
rename to pulser-core/pulser/json/utils.py
diff --git a/pulser/parametrized/__init__.py b/pulser-core/pulser/parametrized/__init__.py
similarity index 100%
rename from pulser/parametrized/__init__.py
rename to pulser-core/pulser/parametrized/__init__.py
diff --git a/pulser/parametrized/decorators.py b/pulser-core/pulser/parametrized/decorators.py
similarity index 100%
rename from pulser/parametrized/decorators.py
rename to pulser-core/pulser/parametrized/decorators.py
diff --git a/pulser/parametrized/paramabc.py b/pulser-core/pulser/parametrized/paramabc.py
similarity index 100%
rename from pulser/parametrized/paramabc.py
rename to pulser-core/pulser/parametrized/paramabc.py
diff --git a/pulser/parametrized/paramobj.py b/pulser-core/pulser/parametrized/paramobj.py
similarity index 100%
rename from pulser/parametrized/paramobj.py
rename to pulser-core/pulser/parametrized/paramobj.py
diff --git a/pulser/parametrized/variable.py b/pulser-core/pulser/parametrized/variable.py
similarity index 100%
rename from pulser/parametrized/variable.py
rename to pulser-core/pulser/parametrized/variable.py
diff --git a/pulser/pulse.py b/pulser-core/pulser/pulse.py
similarity index 100%
rename from pulser/pulse.py
rename to pulser-core/pulser/pulse.py
diff --git a/pulser/py.typed b/pulser-core/pulser/py.typed
similarity index 100%
rename from pulser/py.typed
rename to pulser-core/pulser/py.typed
diff --git a/pulser/register/__init__.py b/pulser-core/pulser/register/__init__.py
similarity index 100%
rename from pulser/register/__init__.py
rename to pulser-core/pulser/register/__init__.py
diff --git a/pulser/register/_patterns.py b/pulser-core/pulser/register/_patterns.py
similarity index 100%
rename from pulser/register/_patterns.py
rename to pulser-core/pulser/register/_patterns.py
diff --git a/pulser/register/_reg_drawer.py b/pulser-core/pulser/register/_reg_drawer.py
similarity index 100%
rename from pulser/register/_reg_drawer.py
rename to pulser-core/pulser/register/_reg_drawer.py
diff --git a/pulser/register/base_register.py b/pulser-core/pulser/register/base_register.py
similarity index 100%
rename from pulser/register/base_register.py
rename to pulser-core/pulser/register/base_register.py
diff --git a/pulser/register/mappable_reg.py b/pulser-core/pulser/register/mappable_reg.py
similarity index 100%
rename from pulser/register/mappable_reg.py
rename to pulser-core/pulser/register/mappable_reg.py
diff --git a/pulser/register/register.py b/pulser-core/pulser/register/register.py
similarity index 100%
rename from pulser/register/register.py
rename to pulser-core/pulser/register/register.py
diff --git a/pulser/register/register3d.py b/pulser-core/pulser/register/register3d.py
similarity index 100%
rename from pulser/register/register3d.py
rename to pulser-core/pulser/register/register3d.py
diff --git a/pulser/register/register_layout.py b/pulser-core/pulser/register/register_layout.py
similarity index 100%
rename from pulser/register/register_layout.py
rename to pulser-core/pulser/register/register_layout.py
diff --git a/pulser/register/special_layouts.py b/pulser-core/pulser/register/special_layouts.py
similarity index 100%
rename from pulser/register/special_layouts.py
rename to pulser-core/pulser/register/special_layouts.py
diff --git a/pulser/sampler/__init__.py b/pulser-core/pulser/sampler/__init__.py
similarity index 100%
rename from pulser/sampler/__init__.py
rename to pulser-core/pulser/sampler/__init__.py
diff --git a/pulser/sampler/noise_model.py b/pulser-core/pulser/sampler/noise_model.py
similarity index 100%
rename from pulser/sampler/noise_model.py
rename to pulser-core/pulser/sampler/noise_model.py
diff --git a/pulser/sampler/sampler.py b/pulser-core/pulser/sampler/sampler.py
similarity index 100%
rename from pulser/sampler/sampler.py
rename to pulser-core/pulser/sampler/sampler.py
diff --git a/pulser/sampler/samples.py b/pulser-core/pulser/sampler/samples.py
similarity index 100%
rename from pulser/sampler/samples.py
rename to pulser-core/pulser/sampler/samples.py
diff --git a/pulser/sequence.py b/pulser-core/pulser/sequence.py
similarity index 100%
rename from pulser/sequence.py
rename to pulser-core/pulser/sequence.py
diff --git a/pulser-core/pulser/simulation/__init__.py b/pulser-core/pulser/simulation/__init__.py
new file mode 100644
index 000000000..44bd25122
--- /dev/null
+++ b/pulser-core/pulser/simulation/__init__.py
@@ -0,0 +1,37 @@
+# Copyright 2020 Pulser Development Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Classes for classical emulation of a Sequence."""
+
+import warnings
+
+main_msg = (
+    "All features previously located in the 'pulser.simulation' module have "
+    "been moved to the 'pulser_simulation' package. "
+)
+
+try:
+    import pulser_simulation
+    from pulser_simulation.simconfig import SimConfig
+    from pulser_simulation.simulation import Simulation
+
+    warnings.warn(
+        main_msg + "It is recommended that all imports of/from "
+        "'pulser.simulation' are changed to 'pulser_simulation'.",
+        stacklevel=2,
+    )
+except ImportError:  # pragma: no cover
+    raise ImportError(
+        main_msg + "Please install the 'pulser_simulation' package and import"
+        " all simulation-related objects directly from it. "
+    )
diff --git a/pulser/waveforms.py b/pulser-core/pulser/waveforms.py
similarity index 100%
rename from pulser/waveforms.py
rename to pulser-core/pulser/waveforms.py
diff --git a/pulser-core/rtd_requirements.txt b/pulser-core/rtd_requirements.txt
new file mode 100644
index 000000000..06d2c0a1a
--- /dev/null
+++ b/pulser-core/rtd_requirements.txt
@@ -0,0 +1,1 @@
+pulser-core/.
diff --git a/pulser-core/setup.py b/pulser-core/setup.py
new file mode 100644
index 000000000..ce059b17d
--- /dev/null
+++ b/pulser-core/setup.py
@@ -0,0 +1,68 @@
+# Copyright 2020 Pulser Development Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from pathlib import PurePath
+
+from setuptools import find_packages, setup
+
+distribution_name = "pulser-core"  # The name on PyPI
+package_name = "pulser"  # The main module name
+current_directory = PurePath(__file__).parent
+
+# Reads the version from the VERSION.txt file
+with open(current_directory.parent / "VERSION.txt", "r") as f:
+    __version__ = f.read().strip()
+
+# Stashes the source code for the local version file
+local_version_fpath = current_directory / package_name / "_version.py"
+with open(local_version_fpath, "r") as f:
+    stashed_version_source = f.read()
+
+# Overwrites the _version.py for the source distribution (reverted at the end)
+with open(local_version_fpath, "w") as f:
+    f.write(f"__version__ = '{__version__}'\n")
+
+setup(
+    name="pulser-core",
+    version=__version__,
+    install_requires=[
+        "matplotlib",
+        "numpy>=1.20",
+        "scipy",
+    ],
+    extras_require={
+        ":python_version == '3.7'": [
+            "backports.cached-property",
+            "typing-extensions",
+        ],
+    },
+    packages=find_packages(),
+    package_data={package_name: ["py.typed"]},
+    include_package_data=True,
+    description="A pulse-level composer for neutral-atom quantum devices.",
+    long_description=open("README.md").read(),
+    long_description_content_type="text/markdown",
+    author="Pulser Development Team",
+    python_requires=">=3.7.0",
+    license="Apache 2.0",
+    classifiers=[
+        "Development Status :: 3 - Alpha",
+        "Programming Language :: Python :: 3",
+    ],
+    url="https://github.com/pasqal-io/Pulser",
+    zip_safe=False,
+)
+
+# Restores the original source code of _version.py
+with open(local_version_fpath, "w") as f:
+    f.write(stashed_version_source)
diff --git a/pulser-simulation/LICENSE b/pulser-simulation/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/pulser-simulation/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/pulser-simulation/MANIFEST.in b/pulser-simulation/MANIFEST.in
new file mode 100644
index 000000000..04f196ac7
--- /dev/null
+++ b/pulser-simulation/MANIFEST.in
@@ -0,0 +1,2 @@
+include README.md
+include LICENSE
diff --git a/pulser-simulation/README.md b/pulser-simulation/README.md
new file mode 100644
index 000000000..b96261e18
--- /dev/null
+++ b/pulser-simulation/README.md
@@ -0,0 +1,21 @@
+# pulser-simulation
+
+Pulser is a framework for composing, simulating and executing **pulse** sequences for neutral-atom quantum devices.
+
+This is the `pulser-simulation` extension, which provides the functionalities needed to emulate `pulser` sequences.
+
+## Installation
+
+The standard Pulser installation,
+
+```bash
+pip install pulser
+```
+
+will automatically install `pulser-simulation`. If you wish to install it on its own, you can also run
+
+```bash
+pip install pulser-simulation
+```
+
+Note that `pulser-core` is a requirement of `pulser-simulation`, so it will be installed if it hasn't been already.
diff --git a/pulser/simulation/__init__.py b/pulser-simulation/pulser_simulation/__init__.py
similarity index 75%
rename from pulser/simulation/__init__.py
rename to pulser-simulation/pulser_simulation/__init__.py
index 3d2ae5021..bcc718488 100644
--- a/pulser/simulation/__init__.py
+++ b/pulser-simulation/pulser_simulation/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2020 Pulser Development Team
+# Copyright 2022 Pulser Development Team
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,5 +13,6 @@
 # limitations under the License.
 """Classes for classical emulation of a Sequence."""
 
-from pulser.simulation.simconfig import SimConfig
-from pulser.simulation.simulation import Simulation
+from pulser_simulation._version import __version__
+from pulser_simulation.simconfig import SimConfig
+from pulser_simulation.simulation import Simulation
diff --git a/pulser-simulation/pulser_simulation/_version.py b/pulser-simulation/pulser_simulation/_version.py
new file mode 100644
index 000000000..08126f2ae
--- /dev/null
+++ b/pulser-simulation/pulser_simulation/_version.py
@@ -0,0 +1,20 @@
+# Copyright 2022 Pulser Development Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from pathlib import PurePath
+
+# Sets the version to the same as 'pulser'.
+version_file_path = PurePath(__file__).parent.parent.parent / "VERSION.txt"
+
+with open(version_file_path, "r") as f:
+    __version__ = f.read().strip()
diff --git a/pulser/simulation/noises.py b/pulser-simulation/pulser_simulation/noises.py
similarity index 100%
rename from pulser/simulation/noises.py
rename to pulser-simulation/pulser_simulation/noises.py
diff --git a/pulser-simulation/pulser_simulation/py.typed b/pulser-simulation/pulser_simulation/py.typed
new file mode 100644
index 000000000..e69de29bb
diff --git a/pulser/simulation/simconfig.py b/pulser-simulation/pulser_simulation/simconfig.py
similarity index 100%
rename from pulser/simulation/simconfig.py
rename to pulser-simulation/pulser_simulation/simconfig.py
diff --git a/pulser/simulation/simresults.py b/pulser-simulation/pulser_simulation/simresults.py
similarity index 100%
rename from pulser/simulation/simresults.py
rename to pulser-simulation/pulser_simulation/simresults.py
diff --git a/pulser/simulation/simulation.py b/pulser-simulation/pulser_simulation/simulation.py
similarity index 99%
rename from pulser/simulation/simulation.py
rename to pulser-simulation/pulser_simulation/simulation.py
index e0408af2e..f8b4e9c6b 100644
--- a/pulser/simulation/simulation.py
+++ b/pulser-simulation/pulser_simulation/simulation.py
@@ -32,8 +32,8 @@
 from pulser._seq_drawer import draw_sequence
 from pulser.register import QubitId
 from pulser.sequence import _TimeSlot
-from pulser.simulation.simconfig import SimConfig
-from pulser.simulation.simresults import (
+from pulser_simulation.simconfig import SimConfig
+from pulser_simulation.simresults import (
     CoherentResults,
     NoisyResults,
     SimulationResults,
diff --git a/pulser-simulation/rtd_requirements.txt b/pulser-simulation/rtd_requirements.txt
new file mode 100644
index 000000000..274cbdffc
--- /dev/null
+++ b/pulser-simulation/rtd_requirements.txt
@@ -0,0 +1,1 @@
+pulser-simulation/.
diff --git a/pulser-simulation/setup.py b/pulser-simulation/setup.py
new file mode 100644
index 000000000..80b68c182
--- /dev/null
+++ b/pulser-simulation/setup.py
@@ -0,0 +1,67 @@
+# Copyright 2022 Pulser Development Team
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import PurePath
+
+from setuptools import find_packages, setup
+
+distribution_name = "pulser-simulation"  # The name on PyPI
+package_name = "pulser_simulation"  # The main module name
+current_directory = PurePath(__file__).parent
+
+# Reads the version from the VERSION.txt file
+with open(current_directory.parent / "VERSION.txt", "r") as f:
+    __version__ = f.read().strip()
+
+# Stashes the source code for the local version file
+local_version_fpath = current_directory / package_name / "_version.py"
+with open(local_version_fpath, "r") as f:
+    stashed_version_source = f.read()
+
+# Overwrites the _version.py for the source distribution (reverted at the end)
+with open(local_version_fpath, "w") as f:
+    f.write(f"__version__ = '{__version__}'\n")
+
+setup(
+    name=distribution_name,
+    version=__version__,
+    install_requires=[
+        f"pulser-core=={__version__}",
+        "qutip>=4.6.3",
+    ],
+    extras_require={
+        ":python_version == '3.7'": [
+            "typing-extensions",
+        ],
+    },
+    packages=find_packages(),
+    package_data={package_name: ["py.typed"]},
+    include_package_data=True,
+    description="An emulator of pulse-level sequences created with Pulser.",
+    long_description=open("README.md").read(),
+    long_description_content_type="text/markdown",
+    author="Pulser Development Team",
+    python_requires=">=3.7.0",
+    license="Apache 2.0",
+    classifiers=[
+        "Development Status :: 3 - Alpha",
+        "Programming Language :: Python :: 3",
+    ],
+    url="https://github.com/pasqal-io/Pulser",
+    zip_safe=False,
+)
+
+# Restores the original source code of _version.py
+with open(local_version_fpath, "w") as f:
+    f.write(stashed_version_source)
diff --git a/pyproject.toml b/pyproject.toml
index d84cc51b8..c0f15345a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,3 +4,4 @@ line-length = 79
 [tool.isort]
 profile = "black"
 line_length = 79
+src_paths = ["pulser-core", "pulser-simulation"]
diff --git a/setup.py b/setup.py
index 5caa815e3..b6c3e5e07 100644
--- a/setup.py
+++ b/setup.py
@@ -12,28 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from setuptools import find_packages, setup
+from setuptools import setup
 
-__version__ = ""
-exec(open("pulser/_version.py").read())
+# Reads the version from the VERSION.txt file
+with open("VERSION.txt", "r") as f:
+    __version__ = f.read().strip()
 
+if "dev" in __version__:
+    raise RuntimeError(
+        "The 'pulser' distribution can only be installed or packaged for "
+        "stable versions. To install the full development version, run "
+        "`pip install -e ./pulser-core -e ./pulser-simulation` instead."
+    )
+
+# Just a meta-package that requires 'pulser-core' and 'pulser-simulation'
 setup(
     name="pulser",
     version=__version__,
     install_requires=[
-        "matplotlib",
-        "numpy>=1.20",
-        "scipy",
-        "qutip>=4.6.3",
+        f"pulser-core=={__version__}",
+        f"pulser-simulation=={__version__}",
     ],
-    extras_require={
-        ":python_version == '3.7'": [
-            "backports.cached-property",
-            "typing-extensions",
-        ],
-    },
-    packages=find_packages(),
-    package_data={"pulser": ["py.typed"]},
     include_package_data=True,
     description="A pulse-level composer for neutral-atom quantum devices.",
     long_description=open("README.md").read(),
diff --git a/tutorials/advanced_features/Simulating Sequences with Errors and Noise.ipynb b/tutorials/advanced_features/Simulating Sequences with Errors and Noise.ipynb
index 8f625e267..0113b4bb9 100644
--- a/tutorials/advanced_features/Simulating Sequences with Errors and Noise.ipynb
+++ b/tutorials/advanced_features/Simulating Sequences with Errors and Noise.ipynb
@@ -37,7 +37,7 @@
     "import qutip\n",
     "\n",
     "from pulser import Register, Pulse, Sequence, Simulation\n",
-    "from pulser.simulation import SimConfig\n",
+    "from pulser_simulation import SimConfig\n",
     "from pulser.devices import Chadoq2\n",
     "from pulser.waveforms import ConstantWaveform, RampWaveform"
    ]
diff --git a/tutorials/advanced_features/State Preparation with the SLM Mask.ipynb b/tutorials/advanced_features/State Preparation with the SLM Mask.ipynb
index 6f32fd09a..ed0645e53 100644
--- a/tutorials/advanced_features/State Preparation with the SLM Mask.ipynb
+++ b/tutorials/advanced_features/State Preparation with the SLM Mask.ipynb
@@ -33,7 +33,7 @@
     "from pulser import Pulse, Sequence, Register\n",
     "from pulser.devices import MockDevice\n",
     "from pulser.waveforms import BlackmanWaveform\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "\n",
     "# Qubit register\n",
     "qubits = {\"q0\": (-5, 0), \"q1\": (0, 0), \"q2\": (5, 0)}\n",
diff --git a/tutorials/applications/Control-Z Gate Sequence.ipynb b/tutorials/applications/Control-Z Gate Sequence.ipynb
index 8e0aaad92..53cf165d1 100644
--- a/tutorials/applications/Control-Z Gate Sequence.ipynb
+++ b/tutorials/applications/Control-Z Gate Sequence.ipynb
@@ -57,7 +57,7 @@
    "source": [
     "from pulser import Pulse, Sequence, Register\n",
     "from pulser.devices import Chadoq2\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.waveforms import BlackmanWaveform, ConstantWaveform"
    ]
   },
diff --git a/tutorials/applications/Using QAOA to solve a MIS problem.ipynb b/tutorials/applications/Using QAOA to solve a MIS problem.ipynb
index e5cc7fe06..76f1bdecb 100644
--- a/tutorials/applications/Using QAOA to solve a MIS problem.ipynb
+++ b/tutorials/applications/Using QAOA to solve a MIS problem.ipynb
@@ -20,7 +20,7 @@
     "import matplotlib.pyplot as plt\n",
     "\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.devices import Chadoq2\n",
     "\n",
     "from scipy.optimize import minimize"
diff --git a/tutorials/quantum_simulation/Bayesian Optimisation for antiferromagnetic state preparation.ipynb b/tutorials/quantum_simulation/Bayesian Optimisation for antiferromagnetic state preparation.ipynb
index 551633ded..45c66d3eb 100644
--- a/tutorials/quantum_simulation/Bayesian Optimisation for antiferromagnetic state preparation.ipynb
+++ b/tutorials/quantum_simulation/Bayesian Optimisation for antiferromagnetic state preparation.ipynb
@@ -46,7 +46,7 @@
     "from skopt import gp_minimize\n",
     "\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.waveforms import InterpolatedWaveform\n",
     "from pulser.devices import Chadoq2"
    ]
diff --git a/tutorials/quantum_simulation/Building 1D Rydberg Crystals.ipynb b/tutorials/quantum_simulation/Building 1D Rydberg Crystals.ipynb
index 23f81a09f..203f16a82 100644
--- a/tutorials/quantum_simulation/Building 1D Rydberg Crystals.ipynb
+++ b/tutorials/quantum_simulation/Building 1D Rydberg Crystals.ipynb
@@ -25,7 +25,7 @@
     "import qutip\n",
     "\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.waveforms import CompositeWaveform, RampWaveform, ConstantWaveform\n",
     "from pulser.devices import MockDevice"
    ]
diff --git a/tutorials/quantum_simulation/Microwave-engineering of programmable XXZ Hamiltonians in arrays of Rydberg atoms.ipynb b/tutorials/quantum_simulation/Microwave-engineering of programmable XXZ Hamiltonians in arrays of Rydberg atoms.ipynb
index 45e0ae741..a9d85d53a 100644
--- a/tutorials/quantum_simulation/Microwave-engineering of programmable XXZ Hamiltonians in arrays of Rydberg atoms.ipynb
+++ b/tutorials/quantum_simulation/Microwave-engineering of programmable XXZ Hamiltonians in arrays of Rydberg atoms.ipynb
@@ -20,7 +20,7 @@
     "\n",
     "import pulser\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.devices import MockDevice, Chadoq2\n",
     "from pulser.waveforms import BlackmanWaveform"
    ]
diff --git a/tutorials/quantum_simulation/Preparing state with antiferromagnetic order in the Ising model.ipynb b/tutorials/quantum_simulation/Preparing state with antiferromagnetic order in the Ising model.ipynb
index e1a2f0803..e09fa891d 100644
--- a/tutorials/quantum_simulation/Preparing state with antiferromagnetic order in the Ising model.ipynb
+++ b/tutorials/quantum_simulation/Preparing state with antiferromagnetic order in the Ising model.ipynb
@@ -22,7 +22,7 @@
     "import qutip\n",
     "\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.waveforms import RampWaveform\n",
     "from pulser.devices import Chadoq2"
    ]
diff --git a/tutorials/quantum_simulation/Shadow estimation for VQS.ipynb b/tutorials/quantum_simulation/Shadow estimation for VQS.ipynb
index b7bd17a06..55131deb9 100644
--- a/tutorials/quantum_simulation/Shadow estimation for VQS.ipynb
+++ b/tutorials/quantum_simulation/Shadow estimation for VQS.ipynb
@@ -103,7 +103,7 @@
     "\n",
     "from pulser import Register, Sequence, Pulse\n",
     "from pulser.devices import Chadoq2\n",
-    "from pulser.simulation import Simulation"
+    "from pulser_simulation import Simulation"
    ]
   },
   {
diff --git a/tutorials/quantum_simulation/Spin chain of 3 atoms in XY mode.ipynb b/tutorials/quantum_simulation/Spin chain of 3 atoms in XY mode.ipynb
index 4c23f0381..fe68acb51 100644
--- a/tutorials/quantum_simulation/Spin chain of 3 atoms in XY mode.ipynb
+++ b/tutorials/quantum_simulation/Spin chain of 3 atoms in XY mode.ipynb
@@ -44,7 +44,7 @@
     "\n",
     "import pulser\n",
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.devices import MockDevice\n",
     "from pulser.waveforms import BlackmanWaveform"
    ]
diff --git a/tutorials/simulating_sequences.ipynb b/tutorials/simulating_sequences.ipynb
index 03fa994e7..b5ec37781 100644
--- a/tutorials/simulating_sequences.ipynb
+++ b/tutorials/simulating_sequences.ipynb
@@ -14,7 +14,7 @@
    "outputs": [],
    "source": [
     "from pulser import Pulse, Sequence, Register\n",
-    "from pulser.simulation import Simulation\n",
+    "from pulser_simulation import Simulation\n",
     "from pulser.waveforms import BlackmanWaveform, RampWaveform\n",
     "from pulser.devices import MockDevice\n",
     "\n",


Test patch:
diff --git a/pulser/tests/__init__.py b/tests/__init__.py
similarity index 100%
rename from pulser/tests/__init__.py
rename to tests/__init__.py
diff --git a/pulser/tests/test_channels.py b/tests/test_channels.py
similarity index 100%
rename from pulser/tests/test_channels.py
rename to tests/test_channels.py
diff --git a/pulser/tests/test_devices.py b/tests/test_devices.py
similarity index 100%
rename from pulser/tests/test_devices.py
rename to tests/test_devices.py
diff --git a/pulser/tests/test_json.py b/tests/test_json.py
similarity index 100%
rename from pulser/tests/test_json.py
rename to tests/test_json.py
diff --git a/pulser/tests/test_parametrized.py b/tests/test_parametrized.py
similarity index 100%
rename from pulser/tests/test_parametrized.py
rename to tests/test_parametrized.py
diff --git a/pulser/tests/test_paramseq.py b/tests/test_paramseq.py
similarity index 100%
rename from pulser/tests/test_paramseq.py
rename to tests/test_paramseq.py
diff --git a/pulser/tests/test_pulse.py b/tests/test_pulse.py
similarity index 100%
rename from pulser/tests/test_pulse.py
rename to tests/test_pulse.py
diff --git a/pulser/tests/test_register.py b/tests/test_register.py
similarity index 100%
rename from pulser/tests/test_register.py
rename to tests/test_register.py
diff --git a/pulser/tests/test_register_layout.py b/tests/test_register_layout.py
similarity index 100%
rename from pulser/tests/test_register_layout.py
rename to tests/test_register_layout.py
diff --git a/pulser/tests/test_sampling_noises.py b/tests/test_sampling_noises.py
similarity index 98%
rename from pulser/tests/test_sampling_noises.py
rename to tests/test_sampling_noises.py
index 32853c654..203671be4 100644
--- a/pulser/tests/test_sampling_noises.py
+++ b/tests/test_sampling_noises.py
@@ -17,7 +17,7 @@
 import pytest
 
 import pulser
-import pulser.simulation.noises as noises
+import pulser_simulation.noises as noises
 from pulser.devices import MockDevice
 from pulser.pulse import Pulse
 from pulser.sampler import sample
diff --git a/pulser/tests/test_sequence.py b/tests/test_sequence.py
similarity index 100%
rename from pulser/tests/test_sequence.py
rename to tests/test_sequence.py
diff --git a/pulser/tests/test_sequence_sampler.py b/tests/test_sequence_sampler.py
similarity index 99%
rename from pulser/tests/test_sequence_sampler.py
rename to tests/test_sequence_sampler.py
index 508475b6a..5031163a4 100644
--- a/pulser/tests/test_sequence_sampler.py
+++ b/tests/test_sequence_sampler.py
@@ -20,6 +20,7 @@
 import pytest
 
 import pulser
+import pulser_simulation
 from pulser.channels import Rydberg
 from pulser.devices import Device, MockDevice
 from pulser.pulse import Pulse
@@ -34,7 +35,7 @@
 def assert_same_samples_as_sim(seq: pulser.Sequence) -> None:
     """Check against the legacy sample extraction in the simulation module."""
     got = sample(seq)
-    want = pulser.Simulation(seq).samples.copy()
+    want = pulser_simulation.Simulation(seq).samples.copy()
 
     def truncate_samples(samples_dict):
         for key, value in samples_dict.items():
diff --git a/pulser/tests/test_simconfig.py b/tests/test_simconfig.py
similarity index 96%
rename from pulser/tests/test_simconfig.py
rename to tests/test_simconfig.py
index 01fdb36d2..ac64f59f2 100644
--- a/pulser/tests/test_simconfig.py
+++ b/tests/test_simconfig.py
@@ -14,7 +14,7 @@
 
 import pytest
 
-from pulser.simulation import SimConfig
+from pulser_simulation import SimConfig
 
 
 def test_init():
diff --git a/pulser/tests/test_simresults.py b/tests/test_simresults.py
similarity index 99%
rename from pulser/tests/test_simresults.py
rename to tests/test_simresults.py
index ec0aecadf..c95cf7084 100644
--- a/pulser/tests/test_simresults.py
+++ b/tests/test_simresults.py
@@ -21,9 +21,9 @@
 
 from pulser import Pulse, Register, Sequence
 from pulser.devices import Chadoq2, MockDevice
-from pulser.simulation import SimConfig, Simulation
-from pulser.simulation.simresults import CoherentResults, NoisyResults
 from pulser.waveforms import BlackmanWaveform
+from pulser_simulation import SimConfig, Simulation
+from pulser_simulation.simresults import CoherentResults, NoisyResults
 
 np.random.seed(123)
 q_dict = {
diff --git a/pulser/tests/test_simulation.py b/tests/test_simulation.py
similarity index 98%
rename from pulser/tests/test_simulation.py
rename to tests/test_simulation.py
index f3aef7db1..169a3c18d 100644
--- a/pulser/tests/test_simulation.py
+++ b/tests/test_simulation.py
@@ -22,8 +22,8 @@
 from pulser import Pulse, Register, Sequence
 from pulser.devices import Chadoq2, MockDevice
 from pulser.register.register_layout import RegisterLayout
-from pulser.simulation import SimConfig, Simulation
 from pulser.waveforms import BlackmanWaveform, ConstantWaveform, RampWaveform
+from pulser_simulation import SimConfig, Simulation
 
 q_dict = {
     "control1": np.array([-4.0, 0.0]),
@@ -74,6 +74,17 @@
 d += 1
 
 
+def test_bad_import():
+    with pytest.warns(
+        UserWarning,
+        match="'pulser.simulation' are changed to 'pulser_simulation'.",
+    ):
+        import pulser.simulation  # noqa: F401
+
+    assert pulser.simulation.Simulation is Simulation
+    assert pulser.simulation.SimConfig is SimConfig
+
+
 def test_initialization_and_construction_of_hamiltonian():
     fake_sequence = {"pulse1": "fake", "pulse2": "fake"}
     with pytest.raises(TypeError, match="sequence has to be a valid"):
diff --git a/pulser/tests/test_waveforms.py b/tests/test_waveforms.py
similarity index 100%
rename from pulser/tests/test_waveforms.py
rename to tests/test_waveforms.py