{"action":"created","issue":{"url":"https://api.github.com/repos/tulip-control/tulip-control/issues/122","labels_url":"https://api.github.com/repos/tulip-control/tulip-control/issues/122/labels{/name}","comments_url":"https://api.github.com/repos/tulip-control/tulip-control/issues/122/comments","events_url":"https://api.github.com/repos/tulip-control/tulip-control/issues/122/events","html_url":"https://github.com/tulip-control/tulip-control/pull/122","id":63475756,"number":122,"title":"Optional cvxopt","user":{"login":"johnyf","id":3135273,"avatar_url":"https://avatars.githubusercontent.com/u/3135273?v=3","gravatar_id":"","url":"https://api.github.com/users/johnyf","html_url":"https://github.com/johnyf","followers_url":"https://api.github.com/users/johnyf/followers","following_url":"https://api.github.com/users/johnyf/following{/other_user}","gists_url":"https://api.github.com/users/johnyf/gists{/gist_id}","starred_url":"https://api.github.com/users/johnyf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnyf/subscriptions","organizations_url":"https://api.github.com/users/johnyf/orgs","repos_url":"https://api.github.com/users/johnyf/repos","events_url":"https://api.github.com/users/johnyf/events{/privacy}","received_events_url":"https://api.github.com/users/johnyf/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":{"login":"slivingston","id":355654,"avatar_url":"https://avatars.githubusercontent.com/u/355654?v=3","gravatar_id":"","url":"https://api.github.com/users/slivingston","html_url":"https://github.com/slivingston","followers_url":"https://api.github.com/users/slivingston/followers","following_url":"https://api.github.com/users/slivingston/following{/other_user}","gists_url":"https://api.github.com/users/slivingston/gists{/gist_id}","starred_url":"https://api.github.com/users/slivingston/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/slivingston/subscriptions","organizations_url":"https://api.github.com/users/slivingston/orgs","repos_url":"https://api.github.com/users/slivingston/repos","events_url":"https://api.github.com/users/slivingston/events{/privacy}","received_events_url":"https://api.github.com/users/slivingston/received_events","type":"User","site_admin":false},"milestone":null,"comments":3,"created_at":"2015-03-22T01:49:28Z","updated_at":"2015-04-01T17:45:14Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/tulip-control/tulip-control/pulls/122","html_url":"https://github.com/tulip-control/tulip-control/pull/122","diff_url":"https://github.com/tulip-control/tulip-control/pull/122.diff","patch_url":"https://github.com/tulip-control/tulip-control/pull/122.patch"},"body":"- create an [\"extra\"](https://pip.pypa.io/en/latest/reference/pip_install.html#examples) installation option called \"hybrid\" that can be installed with `pip install tulip[hybrid]`. The \"hybrid\" option installs `numpy`, `scipy`, `cvxopt`, and `polytope`.\r\n\r\n- make `java` an optional dependency. Java is required by the `jtlv` solver bundled with `tulip`.\r\n  As of 0a6893a69613f06ec50a910ba8884a7fc76b7682, the default solver has been decided to be `gr1c`, because `jtlv` is unmaintained and contains undocumented and/or erroneous behavior (Relevant `tulip-discuss` threads: [“the JTLV solver is dead”](https://sourceforge.net/p/tulip-control/mailman/message/33250491/) and [“`jtlv` outputs `#?#` depending on state numbering”](https://sourceforge.net/p/tulip-control/mailman/tulip-control-discuss/thread/540C44C4.1030806%40gmail.com/#msg32804942)).\r\n\r\n- rm `unknown-commit` from version string, because it does not conform to PEP 440. As a result, `setuptools` fails to recognize such a version as valid, and parses it as `LegacyVersion`. When `pip` is instructed to fetch and install a tarball of a development version of `tulip` (or one of its forks), e.g., using `dependency_links` in `setuptools.setup`), it does not recognize the tarball contents as a valid `tulip` version.\r\n\r\n- use only `install_requires`, and drop `requires` in call to` setuptools.setup`. In the future, the attribute `requires` [may be deprecated](https://wiki.python.org/moin/Distutils/Metadata), in favor of [`install_requires`](https://pythonhosted.org/setuptools/setuptools.html). Documentation on `requires` was found in [`distutils` docs](https://docs.python.org/2/distutils/setupscript.html#relationships-between-distributions-and-packages) and [PEP 314](https://www.python.org/dev/peps/pep-0314/). The packages `scipy`, `matplotlib`, and `networkx` use `install_requires`.\r\n\r\n  My understanding is that `setuptools` considers both `requires` and `install_requires` and the latter is used to make a requirement more specific, in particular `setup_requires` vs `install_requires`. For this reason, it is preferred here (and to avoid duplication and inconsistencies between `install_requires` and `requires`).\r\n\r\n- `nose` added to `tests_require`.\r\n\r\n  Packages in `tests_require` are [only downloaded locally](https://pythonhosted.org/setuptools/setuptools.html#new-and-changed-setup-keywords), *not* installed. They are [installed only if `nose` is already present](http://nose.readthedocs.org/en/latest/api/commands.html), so that the command `python setup.py nosetests` be available. This partly defies the purpose of `tests_require`. If `nose` is added to `setup_requires`, then `nosetests` becomes available. Instead of all this confusion, it is easier to just add `nose` to `install_requires` (since it is a pure-Python dependency). For now it is placed in `tests_require`, because it is deemed optional.\r\n\r\n  Packages from `extras_require` could be duplicated there. However, it may be simpler to add a `requirements.txt` and note that those are the development dependencies, and that tests require them. Relevant: #105.\r\n\r\n- add `bugtrack_url` field to PyPI entry. This is a [PyPI-specific field](http://stackoverflow.com/questions/14459828/how-to-set-bug-tracker-url-in-setup-py-script) that appears frequently, e.g., [see this entry](https://pypi.python.org/pypi/kotti_software/0.1.4). Making it appear on PyPI informs people that GitHub is used, and several users may prefer to click and inspect the `tulip` repository.\r\n\r\n\r\nMotivation:\r\n\r\n1. do not place unnecessary constrains on users. If one wants to use `tulip.spec` (that has pure-Python dependencies) in a package,  the installation of that package should not fail because `cvxopt` fails to build. This prevents reuse of most of the code that exists in `tulip`.\r\n\r\n  This use case applies to @johnyf, for code in `spec`. Currently, the respective `setup.py` first invokes `pip` to install a fork of `tulip` that installs easily. This can be done by using \"dependency links\" in `setuptools.setup`. However, these are deprecated. In the future, installation from outside PyPI will be even harder [PEP 438](https://www.python.org/dev/peps/pep-0438/). The change proposed here allows using the pure Python subset of `tulip` w/o installation failures.\r\n\r\n  As a side-note, `setup.py` may still need to first call `pip` to install `ply` (`pip.main(['install', 'ply == 3.4'])`), so that it can build the parser tables. Nonetheless, this is a clean call to `pip`, and is equivalent to adding `ply` to `install_requires`. It is just a split of when each dependency is going to be installed, in particular shifting `ply` before the call to `setuptools.setup`, so that the tables can be generated to be included in the installation to `site-packages`.\r\n\r\n  Few users that do not need `cvxopt` will make an effort to build `cvxopt`, and instead will avoid using `tulip` in their own work, and all packages that depend on `tulip`.\r\n\r\n  Moreover, for those users that *do* need `cvxopt`, it is psychologically more encouraging to install the pure-Python part of `tulip` w/o trouble and start learning by using it, than to require them to wait (potentially a week or more), until they figure out how to build `cvxopt`.\r\n\r\n\r\n2. If one calls a function that does require `cvxopt` or `polytope`, then an appropriate message can be printed.\r\n  This approach is used by `networkx`, for example [here](https://github.com/networkx/networkx/blob/a822b491c6b53a2819915a8c620e97a36ba6c060/networkx/algorithms/centrality/current_flow_betweenness_subset.py#L102). The difference in `networkx` is that the `ImportError` is intercepted, and the message slightly refined. This is not a major difference from letting an `ImportError` be raised, without verbose `try...except` clauses in the source code. Noting in the readme that `ImportError` guards usage of code that requires optional dependencies should suffice (and can be cleanly reverted, if, in the future, the installation of `cvxopt` becomes robust. Otherwise, the `try...except` clauses would become redundant in case of such a reversal).\r\n\r\n  The default `ImportError` that Python prints is already specific and informative enough. Any user with an elementary understanding of a Python installation will recognize that the package mentioned by an `ImportError` is most likely not present. Then, the user will confirm that this is the case, e.g., by invoking any of the following:\r\n    - `python -c \"import missing_package\"`\r\n    - `pip freeze | grep missing_package`\r\n    - `pip show missing_package`.\r\n\r\n  and install the package with `pip`, or re-install `tulip`, this time with the appropriate \"extra\" enabled.\r\n  \r\n  If the user neither recognizes the `ImportError`, nor searches to read its documentation or a discussion on the web, then teaching Python is beyond the scope of `tulip`.\r\n\r\n\r\n3. An alternative is to separate `tulip` into smaller packages, based on common dependencies. Although this may be preferred in the future, for now it can be avoided by simply defining `extras_require`.\r\n\r\n  Whether `tulip` would be represented better by multiple packages touches on issues of:\r\n\r\n    1. software architecture, and\r\n    2. neat and canonical structuring of theory and algorithms,\r\n\r\nthe discussion of which is beyond the scope of this issue."},"comment":{"url":"https://api.github.com/repos/tulip-control/tulip-control/issues/comments/88571767","html_url":"https://github.com/tulip-control/tulip-control/pull/122#issuecomment-88571767","issue_url":"https://api.github.com/repos/tulip-control/tulip-control/issues/122","id":88571767,"user":{"login":"slivingston","id":355654,"avatar_url":"https://avatars.githubusercontent.com/u/355654?v=3","gravatar_id":"","url":"https://api.github.com/users/slivingston","html_url":"https://github.com/slivingston","followers_url":"https://api.github.com/users/slivingston/followers","following_url":"https://api.github.com/users/slivingston/following{/other_user}","gists_url":"https://api.github.com/users/slivingston/gists{/gist_id}","starred_url":"https://api.github.com/users/slivingston/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/slivingston/subscriptions","organizations_url":"https://api.github.com/users/slivingston/orgs","repos_url":"https://api.github.com/users/slivingston/repos","events_url":"https://api.github.com/users/slivingston/events{/privacy}","received_events_url":"https://api.github.com/users/slivingston/received_events","type":"User","site_admin":false},"created_at":"2015-04-01T17:45:14Z","updated_at":"2015-04-01T17:45:14Z","body":"For conformance with PEP 440, specifically the section [Development releases](https://www.python.org/dev/peps/pep-0440/#developmental-releases), we need to include a number after \"dev\". E.g., compare with the approach of NumPy. On [line 110 of setup.py](https://github.com/numpy/numpy/blob/799a4c7e66b5e6ab6e9d48b29e386c39c991955e/setup.py#L110) in the [numpy repository](https://github.com/numpy/numpy), `.dev0+` followed by \"Unknown\" or a git revision identifier is used, which conforms to PEP 440. @johnyf Can you confirm this will succeed for your use-case? \r\n\r\nThe following diff demonstrates how I will change `tulip/version.py`\r\n```diff\r\n@@ -59,6 +59,6 @@ if os.path.exists(path_to_hashfile):\r\n             break\r\n \r\n     if len(commit_hash) > 0:\r\n-        version += \".dev.\" + commit_hash\r\n+        version += \".dev0+\" + commit_hash\r\n else:\r\n-    version += \".dev\"\r\n+    version += \".dev0+Unknown\"\r\n```\r\n"}}