├── .dockerignore ├── .gitignore ├── .pylintrc ├── Development.md ├── Dockerfile ├── Install_Julia.md ├── LICENSE.txt ├── MANIFEST.in ├── Motivations.md ├── README.md ├── _setup.py ├── bench ├── fermionic_alt_time.py ├── fermionic_nature_time.py ├── from_matrix_alt.py ├── from_matrix_quantum_info.py ├── jordan_wigner_alt_time.py ├── jordan_wigner_nature_time.py ├── pauli_from_list_alt.py ├── pauli_from_list_qinfo.py ├── run_all_bench.py └── run_only_alt.py ├── demos ├── Comparing-qiskit_alt-and-qiskit_nature-VQE-output.ipynb ├── qiskit_alt_demo.ipynb ├── qiskit_alt_demo_jc.ipynb └── qiskit_alt_vqe_demo.ipynb ├── dev_new.md ├── docker_tests ├── Dockerfile ├── README-docker_tests.md ├── run_dockerfile.py └── run_dockerfile.sh ├── examples ├── h2_hamiltonian_alt.py ├── jw_example.jl ├── jw_example.py ├── nature_qubit_hamiltonian_ex.py └── qubit_hamiltonian_ex.py ├── init_test.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── requirements_in_project.txt ├── run_init_tests.sh ├── setup.cfg ├── src └── qiskit_alt │ ├── Project.toml │ ├── __init__.py │ ├── e_struct_julia.py │ ├── e_struct_python.py │ ├── electronic_structure.jl │ ├── electronic_structure.py │ ├── julia_project.py │ ├── juliacall_util.jl │ ├── pauli_operators.py │ └── sys_image │ ├── Project.toml │ ├── compile_exercise_script.jl │ └── packages.jl ├── test └── basic_test.py └── test_juliacall └── basic_juliacall_test.py /.dockerignore: -------------------------------------------------------------------------------- 1 | tcode.jl 2 | tmp* 3 | **/__pycache__ 4 | qiskit-terra/ 5 | **/Manifest.toml 6 | sys_image/*.so 7 | repl_history.jl 8 | bin/ 9 | demos/.ipynb_checkpoints/ 10 | .envrc 11 | 12 | **/*.egg-info 13 | **/*.log 14 | 15 | .git 16 | **/.git 17 | julia 18 | venv 19 | conda_venv 20 | env 21 | dist 22 | docker_tests 23 | demos 24 | .direnv 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tcode.jl 2 | tmp* 3 | env/ 4 | venv/ 5 | dist/ 6 | */__pycache__ 7 | */*/__pycache__ 8 | qiskit-terra/ 9 | Manifest.toml 10 | **/*.so 11 | repl_history.jl 12 | bin/ 13 | .ipynb_checkpoints/ 14 | .envrc 15 | src/qiskit_alt.egg-info/ 16 | /qiskit_alt.egg-info/ 17 | qiskit_alt.log 18 | /julia 19 | *.disable 20 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-allow-list= 7 | 8 | # A comma-separated list of package or module names from where C extensions may 9 | # be loaded. Extensions are loading into the active Python interpreter and may 10 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 11 | # for backward compatibility.) 12 | extension-pkg-whitelist= 13 | 14 | # Return non-zero exit code if any of these messages/categories are detected, 15 | # even if score is above --fail-under value. Syntax same as enable. Messages 16 | # specified are enabled, while categories only check already-enabled messages. 17 | fail-on= 18 | 19 | # Specify a score threshold to be exceeded before program exits with error. 20 | fail-under=10.0 21 | 22 | # Files or directories to be skipped. They should be base names, not paths. 23 | ignore=CVS 24 | 25 | # Add files or directories matching the regex patterns to the ignore-list. The 26 | # regex matches against paths and can be in Posix or Windows format. 27 | ignore-paths= 28 | # This does not work ignore-paths=uselibjulia* 29 | 30 | # Files or directories matching the regex patterns are skipped. The regex 31 | # matches against base names, not paths. 32 | ignore-patterns=uselibjulia* 33 | 34 | # Python code to execute, usually for sys.path manipulation such as 35 | # pygtk.require(). 36 | #init-hook= 37 | 38 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 39 | # number of processors available to use. 40 | jobs=1 41 | 42 | # Control the amount of potential inferred values when inferring a single 43 | # object. This can help the performance when dealing with large functions or 44 | # complex, nested conditions. 45 | limit-inference-results=100 46 | 47 | # List of plugins (as comma separated values of python module names) to load, 48 | # usually to register additional checkers. 49 | load-plugins= 50 | 51 | # Pickle collected data for later comparisons. 52 | persistent=yes 53 | 54 | # Minimum Python version to use for version dependent checks. Will default to 55 | # the version used to run pylint. 56 | py-version=3.9 57 | 58 | # When enabled, pylint would attempt to guess common misconfiguration and emit 59 | # user-friendly hints instead of false-positive error messages. 60 | suggestion-mode=yes 61 | 62 | # Allow loading of arbitrary C extensions. Extensions are imported into the 63 | # active Python interpreter and may run arbitrary code. 64 | unsafe-load-any-extension=no 65 | 66 | 67 | [MESSAGES CONTROL] 68 | 69 | # Only show warnings with the listed confidence levels. Leave empty to show 70 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 71 | confidence= 72 | 73 | # Disable the message, report, category or checker with the given id(s). You 74 | # can either give multiple identifiers separated by comma (,) or put this 75 | # option multiple times (only on the command line, not in the configuration 76 | # file where it should appear only once). You can also use "--disable=all" to 77 | # disable everything first and then reenable specific checks. For example, if 78 | # you want to run only the similarities checker, you can use "--disable=all 79 | # --enable=similarities". If you want to run only the classes checker, but have 80 | # no Warning level messages displayed, use "--disable=all --enable=classes 81 | # --disable=W". 82 | disable=raw-checker-failed, 83 | bad-inline-option, 84 | locally-disabled, 85 | file-ignored, 86 | suppressed-message, 87 | useless-suppression, 88 | deprecated-pragma, 89 | use-symbolic-message-instead, # GJL. added after this line 90 | line-too-long, 91 | missing-function-docstring, 92 | invalid-name, 93 | superfluous-parens, 94 | logging-fstring-interpolation, # GJL. Debate online about this. Defer decision 95 | logging-too-many-args # Seems to be erroneously emitted 96 | 97 | # Enable the message, report, category or checker with the given id(s). You can 98 | # either give multiple identifier separated by comma (,) or put this option 99 | # multiple time (only on the command line, not in the configuration file where 100 | # it should appear only once). See also the "--disable" option for examples. 101 | enable=c-extension-no-member 102 | 103 | 104 | [REPORTS] 105 | 106 | # Python expression which should return a score less than or equal to 10. You 107 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 108 | # which contain the number of messages in each category, as well as 'statement' 109 | # which is the total number of statements analyzed. This score is used by the 110 | # global evaluation report (RP0004). 111 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 112 | 113 | # Template used to display messages. This is a python new-style format string 114 | # used to format the message information. See doc for all details. 115 | #msg-template= 116 | 117 | # Set the output format. Available formats are text, parseable, colorized, json 118 | # and msvs (visual studio). You can also give a reporter class, e.g. 119 | # mypackage.mymodule.MyReporterClass. 120 | output-format=text 121 | 122 | # Tells whether to display a full report or only the messages. 123 | reports=no 124 | 125 | # Activate the evaluation score. 126 | score=yes 127 | 128 | 129 | [REFACTORING] 130 | 131 | # Maximum number of nested blocks for function / method body 132 | max-nested-blocks=5 133 | 134 | # Complete name of functions that never returns. When checking for 135 | # inconsistent-return-statements if a never returning function is called then 136 | # it will be considered as an explicit return statement and no message will be 137 | # printed. 138 | never-returning-functions=sys.exit,argparse.parse_error 139 | 140 | 141 | [MISCELLANEOUS] 142 | 143 | # List of note tags to take in consideration, separated by a comma. 144 | notes=FIXME, 145 | XXX, 146 | TODO 147 | 148 | # Regular expression of note tags to take in consideration. 149 | #notes-rgx= 150 | 151 | 152 | [SIMILARITIES] 153 | 154 | # Comments are removed from the similarity computation 155 | ignore-comments=yes 156 | 157 | # Docstrings are removed from the similarity computation 158 | ignore-docstrings=yes 159 | 160 | # Imports are removed from the similarity computation 161 | ignore-imports=no 162 | 163 | # Signatures are removed from the similarity computation 164 | ignore-signatures=no 165 | 166 | # Minimum lines number of a similarity. 167 | min-similarity-lines=4 168 | 169 | 170 | [BASIC] 171 | 172 | # Naming style matching correct argument names. 173 | argument-naming-style=snake_case 174 | 175 | # Regular expression matching correct argument names. Overrides argument- 176 | # naming-style. 177 | #argument-rgx= 178 | 179 | # Naming style matching correct attribute names. 180 | attr-naming-style=snake_case 181 | 182 | # Regular expression matching correct attribute names. Overrides attr-naming- 183 | # style. 184 | #attr-rgx= 185 | 186 | # Bad variable names which should always be refused, separated by a comma. 187 | bad-names=foo, 188 | bar, 189 | baz, 190 | toto, 191 | tutu, 192 | tata 193 | 194 | # Bad variable names regexes, separated by a comma. If names match any regex, 195 | # they will always be refused 196 | bad-names-rgxs= 197 | 198 | # Naming style matching correct class attribute names. 199 | class-attribute-naming-style=any 200 | 201 | # Regular expression matching correct class attribute names. Overrides class- 202 | # attribute-naming-style. 203 | #class-attribute-rgx= 204 | 205 | # Naming style matching correct class constant names. 206 | class-const-naming-style=UPPER_CASE 207 | 208 | # Regular expression matching correct class constant names. Overrides class- 209 | # const-naming-style. 210 | #class-const-rgx= 211 | 212 | # Naming style matching correct class names. 213 | class-naming-style=PascalCase 214 | 215 | # Regular expression matching correct class names. Overrides class-naming- 216 | # style. 217 | #class-rgx= 218 | 219 | # Naming style matching correct constant names. 220 | const-naming-style=UPPER_CASE 221 | 222 | # Regular expression matching correct constant names. Overrides const-naming- 223 | # style. 224 | #const-rgx= 225 | 226 | # Minimum line length for functions/classes that require docstrings, shorter 227 | # ones are exempt. 228 | docstring-min-length=-1 229 | 230 | # Naming style matching correct function names. 231 | function-naming-style=snake_case 232 | 233 | # Regular expression matching correct function names. Overrides function- 234 | # naming-style. 235 | #function-rgx= 236 | 237 | # Good variable names which should always be accepted, separated by a comma. 238 | good-names=i, 239 | j, 240 | k, 241 | ex, 242 | Run, 243 | _ 244 | 245 | # Good variable names regexes, separated by a comma. If names match any regex, 246 | # they will always be accepted 247 | good-names-rgxs= 248 | 249 | # Include a hint for the correct naming format with invalid-name. 250 | include-naming-hint=no 251 | 252 | # Naming style matching correct inline iteration names. 253 | inlinevar-naming-style=any 254 | 255 | # Regular expression matching correct inline iteration names. Overrides 256 | # inlinevar-naming-style. 257 | #inlinevar-rgx= 258 | 259 | # Naming style matching correct method names. 260 | method-naming-style=snake_case 261 | 262 | # Regular expression matching correct method names. Overrides method-naming- 263 | # style. 264 | #method-rgx= 265 | 266 | # Naming style matching correct module names. 267 | module-naming-style=snake_case 268 | 269 | # Regular expression matching correct module names. Overrides module-naming- 270 | # style. 271 | #module-rgx= 272 | 273 | # Colon-delimited sets of names that determine each other's naming style when 274 | # the name regexes allow several styles. 275 | name-group= 276 | 277 | # Regular expression which should only match function or class names that do 278 | # not require a docstring. 279 | no-docstring-rgx=^_ 280 | 281 | # List of decorators that produce properties, such as abc.abstractproperty. Add 282 | # to this list to register other decorators that produce valid properties. 283 | # These decorators are taken in consideration only for invalid-name. 284 | property-classes=abc.abstractproperty 285 | 286 | # Naming style matching correct variable names. 287 | variable-naming-style=snake_case 288 | 289 | # Regular expression matching correct variable names. Overrides variable- 290 | # naming-style. 291 | #variable-rgx= 292 | 293 | 294 | [LOGGING] 295 | 296 | # The type of string formatting that logging methods do. `old` means using % 297 | # formatting, `new` is for `{}` formatting. 298 | #logging-format-style=old 299 | logging-format-style=new # GJL 300 | 301 | # Logging modules to check that the string format arguments are in logging 302 | # function parameter format. 303 | logging-modules=logging 304 | 305 | 306 | [STRING] 307 | 308 | # This flag controls whether inconsistent-quotes generates a warning when the 309 | # character used as a quote delimiter is used inconsistently within a module. 310 | check-quote-consistency=no 311 | 312 | # This flag controls whether the implicit-str-concat should generate a warning 313 | # on implicit string concatenation in sequences defined over several lines. 314 | check-str-concat-over-line-jumps=no 315 | 316 | 317 | [FORMAT] 318 | 319 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 320 | expected-line-ending-format= 321 | 322 | # Regexp for a line that is allowed to be longer than the limit. 323 | ignore-long-lines=^\s*(# )??$ 324 | 325 | # Number of spaces of indent required inside a hanging or continued line. 326 | indent-after-paren=4 327 | 328 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 329 | # tab). 330 | indent-string=' ' 331 | 332 | # Maximum number of characters on a single line. 333 | max-line-length=100 334 | 335 | # Maximum number of lines in a module. 336 | max-module-lines=1000 337 | 338 | # Allow the body of a class to be on the same line as the declaration if body 339 | # contains single statement. 340 | single-line-class-stmt=no 341 | 342 | # Allow the body of an if to be on the same line as the test if there is no 343 | # else. 344 | single-line-if-stmt=no 345 | 346 | 347 | [VARIABLES] 348 | 349 | # List of additional names supposed to be defined in builtins. Remember that 350 | # you should avoid defining new builtins when possible. 351 | additional-builtins= 352 | 353 | # Tells whether unused global variables should be treated as a violation. 354 | allow-global-unused-variables=yes 355 | 356 | # List of names allowed to shadow builtins 357 | allowed-redefined-builtins= 358 | 359 | # List of strings which can identify a callback function by name. A callback 360 | # name must start or end with one of those strings. 361 | callbacks=cb_, 362 | _cb 363 | 364 | # A regular expression matching the name of dummy variables (i.e. expected to 365 | # not be used). 366 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 367 | 368 | # Argument names that match this expression will be ignored. Default to name 369 | # with leading underscore. 370 | ignored-argument-names=_.*|^ignored_|^unused_ 371 | 372 | # Tells whether we should check for unused import in __init__ files. 373 | init-import=no 374 | 375 | # List of qualified module names which can have objects that can redefine 376 | # builtins. 377 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 378 | 379 | 380 | [TYPECHECK] 381 | 382 | # List of decorators that produce context managers, such as 383 | # contextlib.contextmanager. Add to this list to register other decorators that 384 | # produce valid context managers. 385 | contextmanager-decorators=contextlib.contextmanager 386 | 387 | # List of members which are set dynamically and missed by pylint inference 388 | # system, and so shouldn't trigger E1101 when accessed. Python regular 389 | # expressions are accepted. 390 | generated-members= 391 | 392 | # Tells whether missing members accessed in mixin class should be ignored. A 393 | # class is considered mixin if its name matches the mixin-class-rgx option. 394 | ignore-mixin-members=yes 395 | 396 | # Tells whether to warn about missing members when the owner of the attribute 397 | # is inferred to be None. 398 | ignore-none=yes 399 | 400 | # This flag controls whether pylint should warn about no-member and similar 401 | # checks whenever an opaque object is returned when inferring. The inference 402 | # can return multiple potential results while evaluating a Python object, but 403 | # some branches might not be evaluated, which results in partial inference. In 404 | # that case, it might be useful to still emit no-member and other checks for 405 | # the rest of the inferred objects. 406 | ignore-on-opaque-inference=yes 407 | 408 | # List of class names for which member attributes should not be checked (useful 409 | # for classes with dynamically set attributes). This supports the use of 410 | # qualified names. 411 | ignored-classes=optparse.Values,thread._local,_thread._local 412 | 413 | # List of module names for which member attributes should not be checked 414 | # (useful for modules/projects where namespaces are manipulated during runtime 415 | # and thus existing member attributes cannot be deduced by static analysis). It 416 | # supports qualified module names, as well as Unix pattern matching. 417 | ignored-modules= 418 | 419 | # Show a hint with possible names when a member name was not found. The aspect 420 | # of finding the hint is based on edit distance. 421 | missing-member-hint=yes 422 | 423 | # The minimum edit distance a name should have in order to be considered a 424 | # similar match for a missing member name. 425 | missing-member-hint-distance=1 426 | 427 | # The total number of similar names that should be taken in consideration when 428 | # showing a hint for a missing member. 429 | missing-member-max-choices=1 430 | 431 | # Regex pattern to define which classes are considered mixins ignore-mixin- 432 | # members is set to 'yes' 433 | mixin-class-rgx=.*[Mm]ixin 434 | 435 | # List of decorators that change the signature of a decorated function. 436 | signature-mutators= 437 | 438 | 439 | [SPELLING] 440 | 441 | # Limits count of emitted suggestions for spelling mistakes. 442 | max-spelling-suggestions=4 443 | 444 | # Spelling dictionary name. Available dictionaries: none. To make it work, 445 | # install the 'python-enchant' package. 446 | spelling-dict= 447 | 448 | # List of comma separated words that should be considered directives if they 449 | # appear and the beginning of a comment and should not be checked. 450 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 451 | 452 | # List of comma separated words that should not be checked. 453 | spelling-ignore-words= 454 | 455 | # A path to a file that contains the private dictionary; one word per line. 456 | spelling-private-dict-file= 457 | 458 | # Tells whether to store unknown words to the private dictionary (see the 459 | # --spelling-private-dict-file option) instead of raising a message. 460 | spelling-store-unknown-words=no 461 | 462 | 463 | [DESIGN] 464 | 465 | # List of regular expressions of class ancestor names to ignore when counting 466 | # public methods (see R0903) 467 | exclude-too-few-public-methods= 468 | 469 | # List of qualified class names to ignore when counting class parents (see 470 | # R0901) 471 | ignored-parents= 472 | 473 | # Maximum number of arguments for function / method. 474 | max-args=5 475 | 476 | # Maximum number of attributes for a class (see R0902). 477 | max-attributes=7 478 | 479 | # Maximum number of boolean expressions in an if statement (see R0916). 480 | max-bool-expr=5 481 | 482 | # Maximum number of branch for function / method body. 483 | max-branches=12 484 | 485 | # Maximum number of locals for function / method body. 486 | max-locals=15 487 | 488 | # Maximum number of parents for a class (see R0901). 489 | max-parents=7 490 | 491 | # Maximum number of public methods for a class (see R0904). 492 | max-public-methods=20 493 | 494 | # Maximum number of return / yield for function / method body. 495 | max-returns=6 496 | 497 | # Maximum number of statements in function / method body. 498 | max-statements=50 499 | 500 | # Minimum number of public methods for a class (see R0903). 501 | min-public-methods=2 502 | 503 | 504 | [CLASSES] 505 | 506 | # Warn about protected attribute access inside special methods 507 | check-protected-access-in-special-methods=no 508 | 509 | # List of method names used to declare (i.e. assign) instance attributes. 510 | defining-attr-methods=__init__, 511 | __new__, 512 | setUp, 513 | __post_init__ 514 | 515 | # List of member names, which should be excluded from the protected access 516 | # warning. 517 | exclude-protected=_asdict, 518 | _fields, 519 | _replace, 520 | _source, 521 | _make 522 | 523 | # List of valid names for the first argument in a class method. 524 | valid-classmethod-first-arg=cls 525 | 526 | # List of valid names for the first argument in a metaclass class method. 527 | valid-metaclass-classmethod-first-arg=cls 528 | 529 | 530 | [IMPORTS] 531 | 532 | # List of modules that can be imported at any level, not just the top level 533 | # one. 534 | allow-any-import-level= 535 | 536 | # Allow wildcard imports from modules that define __all__. 537 | allow-wildcard-with-all=no 538 | 539 | # Analyse import fallback blocks. This can be used to support both Python 2 and 540 | # 3 compatible code, which means that the block might have code that exists 541 | # only in one or another interpreter, leading to false positives when analysed. 542 | analyse-fallback-blocks=no 543 | 544 | # Deprecated modules which should not be used, separated by a comma. 545 | deprecated-modules= 546 | 547 | # Output a graph (.gv or any supported image format) of external dependencies 548 | # to the given file (report RP0402 must not be disabled). 549 | ext-import-graph= 550 | 551 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 552 | # external) dependencies to the given file (report RP0402 must not be 553 | # disabled). 554 | import-graph= 555 | 556 | # Output a graph (.gv or any supported image format) of internal dependencies 557 | # to the given file (report RP0402 must not be disabled). 558 | int-import-graph= 559 | 560 | # Force import order to recognize a module as part of the standard 561 | # compatibility libraries. 562 | known-standard-library= 563 | 564 | # Force import order to recognize a module as part of a third party library. 565 | known-third-party=enchant 566 | 567 | # Couples of modules and preferred modules, separated by a comma. 568 | preferred-modules= 569 | 570 | 571 | [EXCEPTIONS] 572 | 573 | # Exceptions that will emit a warning when being caught. Defaults to 574 | # "BaseException, Exception". 575 | overgeneral-exceptions=BaseException, 576 | Exception 577 | -------------------------------------------------------------------------------- /Development.md: -------------------------------------------------------------------------------- 1 | # Notes for Developers 2 | 3 | * [Packages supporting qiskit-alt](#packages-supporting-qiskit-alt) 4 | 5 | * [Resources](#resources) 6 | 7 | * [Development environment](#development-environment) 8 | 9 | * [Details](#details) 10 | 11 | * [Revise](#revise) Automatically compiling code into a live session. 12 | 13 | * [Example Development Session](#example-development-session) 14 | 15 | * [Making a subdirectory for examples](#making-a-subdirectory-for-examples) 16 | 17 | * [Compilation of system image](#compiling-a-system-image) 18 | 19 | 20 | ### Packages supporting qiskit_alt 21 | 22 | The following packages are developed in concert with `qiskit_alt`. 23 | Some are Julia packages. Some are Python packages. The latter were written specifically to support `qiskit_alt`. 24 | 25 | #### Julia packages 26 | * [ElectronicStructure.jl](https://github.com/Qiskit-Extensions/ElectronicStructure.jl) 27 | * [QuantumOps.jl](https://github.com/Qiskit-Extensions/QuantumOps.jl) 28 | * [QiskitQuantumInfo.jl](https://github.com/Qiskit-Extensions/QiskitQuantumInfo.jl) 29 | 30 | #### Python packages 31 | * [qiskit_alt](https://github.com/Qiskit-Extensions/qiskit-alt) 32 | * [julia_project](https://github.com/jlapeyre/julia_project) 33 | * [julia_project_basic](https://github.com/jlapeyre/julia_project_basic) 34 | * [find_julia](https://github.com/jlapeyre/find_julia) 35 | * [julia_semver](https://github.com/jlapeyre/julia_semver) 36 | 37 | #### Other essential packages 38 | 39 | * [QuantumRegistry](https://github.com/Qiskit-Extensions/QuantumRegistry) 40 | This tells the Julia package manager where to find our Julia packages. It is installed automatically 41 | by `qiskit_alt`. It is maintained by [`LocalRegsitry`](https://github.com/GunnarFarneback/LocalRegistry.jl) 42 | 43 | * [electronic_structure_data](https://github.com/Qiskit-Extensions/electronic_structure_data) 44 | This data is used to exercise code paths when compiling. 45 | 46 | #### Other non-essential packages 47 | 48 | * [QuantumOpsDemos](https://github.com/Qiskit-Extensions/QuantumOpsDemos) 49 | * [ElectronicStructurePySCF.jl](https://github.com/Qiskit-Extensions/ElectronicStructurePySCF.jl) 50 | `qiskit_alt` no longer depends on this package. However, it is useful when working from the 51 | other Julia packages from julia. It was also used to generate [electronic_structure_data](https://github.com/Qiskit-Extensions/electronic_structure_data). 52 | Most of what is written here is generic to Julia. A bit is generic to Julia within Python. 53 | 54 | 55 | ### Resources 56 | 57 | * [Incorporating Julia Into Python Programs](https://www.peterbaumgartner.com/blog/incorporating-julia-into-python-programs/) discusses using pyjulia and Docker. 58 | 59 | * [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/). Essential reading for 60 | scientific projects in Julia. It is largely about avoiding big performance blunders. For example an array of these 61 | ```julia 62 | struct TypeA 63 | x::Integer 64 | end 65 | ``` 66 | will always have pointers to `TypeA` because each element could hold, for example, an `Int64` or an `Int32`, even if 67 | all are in fact `Int64`. The compiler can't tell from the type information. 68 | So `a = [TypeA(1), TypeA(42)]` will be an array of generic `TypeA` even though each element (thus far) is a 64 bit integer. 69 | Pushing `TypeA(Int32(1))` to `a` is allowed. 70 | On the other hand, if I define 71 | ```julia 72 | struct TypeB{T<:Integer} 73 | x::T 74 | end 75 | ``` 76 | then `a = [TypeB(1), TypeB(42)]` will be an array of `TypeB{Int64}` and the storage will be implemented as a packed array 77 | of 64 bit integers. Trying to push a `TypeB(Int32(1))` to `a` will throw an error. 78 | So the performance tips are largely not complicated optimizations, but rather idiomatic coding habits to avoid 79 | performance penalties. 80 | 81 | ### Development environment 82 | 83 | Before describing a workflow, we explain how to use the package manager. 84 | Here are three ways (there are more): 85 | 86 | * From the Julia REPL: 87 | ```julia 88 | julia> import Pkg 89 | julia> Pkg.activate(".") 90 | julia> Pkg.status() 91 | julia> Pkg.add("APackage") 92 | julia> Pkg.rm("APackage") 93 | julia> Pkg.develop(path="/path/to/package/source/tree/") 94 | 95 | ``` 96 | * From the package manager REPL. Enter the jula repl and hit `]`. 97 | ```julia 98 | (@v1.7) pkg> activate "." 99 | (qiskit_alt) pkg> status 100 | (qiskit_alt) pkg> add APackage 101 | (qiskit_alt) pkg> develop /path/to/package/source/tree/ 102 | ``` 103 | 104 | * From Python. Do `Pkg = qiskit_alt.project.julia.Pkg`. Then the syntax is the same as from the Julia REPL. 105 | 106 | The following enables a workflow, which is explained in more detail further below. 107 | 108 | * clone `qiskit_alt` 109 | * If you install `qiskit_alt` as described [here](https://github.com/Qiskit-Extensions/qiskit-alt#installation-and-configuration-notes) 110 | then [QuantumRegistry](https://github.com/Qiskit-Extensions/QuantumRegistry) will be installed for you. 111 | * For development, you you need to clone these packages `QuantumOps`, `ElectronicStructure`, and `QiskitQuantumInfo`. 112 | You can do this in one of two ways. 113 | * Clone them via `Pkg.develop("QuantumOps")`. This looks up the url in your registries and clones to `~/.julia/dev/`. 114 | It also writes the path to the downloaded tree to the `Manifest.toml` of your current project, so that your project 115 | loads the package from the development tree. 116 | * Clone them manually into a directory of your choice. Then, in order for the project to know where you put them, 117 | you have to do `Pkg.develop(path="/path/to/source/Package")` after activating the `qiskit_alt` project. 118 | Do this for each package. 119 | I prefer this second method because `~/.julia/dev/` becomes cluttered. But the first method is simpler. 120 | 121 | * If you chose the second option above, an alternative way to "develop" the packages after cloning is to start the Julia REPL 122 | and enter the package manager mode with `]`. 123 | Then do `dev ~/path/to/QuantumOps`. 124 | Repeat this for each package you want to develop. 125 | 126 | * Start ipython. Before doing `import qiskit_alt`, enable the magics listed in the section [Revise](#revise). 127 | Note that this only works with `pyjulia`, not with `juliacall`. 128 | 129 | Now you can edit the source in the cloned Julia packages and the changes will be reflected in your ipython REPL without restarting. 130 | To revert to the production environment, enter `free QuantumOps`, etc. at the Julia package manager prompt. 131 | 132 | When a package is "developed", `qiskit_alt` will use the editable version in `~/path/to/QuantumOps`. 133 | When the package is "freed", `qiskit_alt` will use the immutable versions stored in your local package depot: 134 | ```shell 135 | > ls ~/.julia/packages/QuantumOps/ 136 | I1StV oJT7c TdAEP VCfDa wv1sy 137 | ``` 138 | Each subdirectory contains an immutable snapshot or version of the package identified with a uuid. 139 | Your `Manifest.toml` includes these uuids, for all packages, including in the standard library. 140 | So it can be used to reproduce an environment with exactly these immutable versions of packages. 141 | 142 | #### Details 143 | 144 | * A [Registry](https://pkgdocs.julialang.org/v1/registries/) is a directory structure that associates package UUIDS 145 | with package metadata, such as a package name, available versions, and download url. These registries are stored 146 | in `~/.julia/registries/`. When Julia resolves package requirements for a project, it refers to these registries. 147 | The[General Registry](https://github.com/JuliaRegistries/General) contains thousands of packages. 148 | We add to this our own [QuantumRegistry](https://github.com/Qiskit-Extensions/QuantumRegistry). 149 | A package is identified by its UUID. You can use this to distinguish packages with the same name. 150 | If you type `julia> Pkg.add("MyPackage")`. Julia looks for a package by that name in your registries. 151 | 152 | * A Julia [project](https://pkgdocs.julialang.org/v1/environments/) 153 | may be specified by a `Project.toml` file. It contains data on required packages and version 154 | ranges, as well as other information. It does *not* record from where a package was downloaded. 155 | When we refer to the `qiskit_alt` project, we mean the Julia project specified by the top level `Project.toml` file. 156 | There is a another project specified by `./sys_image/Project.toml`. This is the project, 157 | a different set of packages, instantiated when compiling a system image for `qiskit_alt`. 158 | If you activate a project say, via `Pkg.activate(".")`, then `Pkg.add` and `Pkg.rm` modify `Project.toml`. 159 | 160 | * The `Manifest.toml` file associated with a `Project.toml` records how your system has instantiated 161 | the project. It records a package dependency graph with metadata including download url. 162 | For each package, a SHA hash of the read-only directory containing the source is included in `Manifest.toml`. 163 | This corresponds, roughly to a package version. When you "develop" and "free" a package, it's url is changed 164 | in `Manifest.toml`. We don't keep `Manifest.toml` under VCS. It is always safe to delete it. It will 165 | be rebuilt from the `Project.toml` and the registries. 166 | 167 | The Julia packages backing qiskit_alt are registered in this registry 168 | [https://github.com/Qiskit-Extensions/QuantumRegistry](https://github.com/Qiskit-Extensions/QuantumRegistry). 169 | The registered packages themselves are also on `github.com`. 170 | While developing, we want our `Manifest.toml` to point to local editable source trees rather than 171 | copies of immutable sources trees that have been downloaded. When developing Python, you typically 172 | restart frequently because you are working with precompiled components. 173 | Julia currently recompiles quite a bit on restart (Julia caches some parts of the compilation 174 | in `.ji` files. And there are plans to 175 | [cache native code](https://github.com/JuliaLang/julia/issues/30488). 176 | But for now, it is recompiled.) 177 | One way around this is to recompile code snippets into a running session with 178 | [`Revise.jl`](https://github.com/timholy/Revise.jl). 179 | 180 | * [`Revise.jl`](https://github.com/timholy/Revise.jl). `Revise` monitors source files. When you edit and 181 | save a file, a diff is made and the changed code is evaluated in the appropriate scope. 182 | The change is available immediately at the REPL without restarting the runtime. 183 | 184 | We need to change the location of the Julia packages, for example `QuantumOps.jl` from the ibm server to a local source tree. 185 | 186 | * To tell the active project to load a package from a development source tree, use `dev(elop)`. 187 | Use `free` to return to an immuatable copy downloaded from a registry url. 188 | Assuming you have a local clone of `QuantumOps.jl`, you can do the following. 189 | ```julia 190 | julia> # hit ']' to enter package management mode 191 | (v1.7) pkg> activate . 192 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 193 | 194 | (qiskit_alt) pkg> dev /home/username/quantum_repos/QuantumOps 195 | Resolving package versions... 196 | Updating `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 197 | [d0cc4389] ~ QuantumOps v0.1.1 ⇒ v0.1.1 `~/quantum_repos/QuantumOps` 198 | Updating `~/myrepos/quantum_repos/qiskit_alt/Manifest.toml` 199 | [d0cc4389] ~ QuantumOps v0.1.1 ⇒ v0.1.1 `~/quantum_repos/QuantumOps` 200 | 201 | (qiskit_alt) pkg> free QuantumOps 202 | Resolving package versions... 203 | Updating `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 204 | [d0cc4389] ~ QuantumOps v0.1.1 `~/quantum_repos/QuantumOps` ⇒ v0.1.1 205 | Updating `~/myrepos/quantum_repos/qiskit_alt/Manifest.toml` 206 | [d0cc4389] ~ QuantumOps v0.1.1 `~/quantum_repos/QuantumOps` ⇒ v0.1.1 207 | ``` 208 | 209 | ### Revise 210 | 211 | Note: The following only works with `pyjulia` not `juliacall`. 212 | From the link above, [Incorporating Julia Into Python Programs](https://www.peterbaumgartner.com/blog/incorporating-julia-into-python-programs/), 213 | I find the following 214 | ```python 215 | In [1]: %config JuliaMagics.revise = True 216 | 217 | In [2]: %load_ext julia.magic 218 | Initializing Julia interpreter. This may take some time... 219 | 220 | In [3]: from qiskit_alt import QuantumOps, Main 221 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 222 | ``` 223 | 224 | Indeed, changes to the `QuantumOps` source are reflected immediately at the ipython REPL. 225 | For example, a variable `foo = 1` added to `src/QuantumOps.jl` is visible 226 | in ipython as `QuantumOps.foo`. 227 | 228 | I'd like to find a way to enable `Revise` without using magics. I have not yet discovered this. 229 | 230 | ### Example Development Session 231 | 232 | * Install the [`LocalRegsitry`](https://github.com/GunnarFarneback/LocalRegistry.jl) package to manage our registry. 233 | 234 | ```julia 235 | # julia repl prompt 236 | julia> 237 | 238 | # Hit `;` to enter shell mode and check our current directory. It is qiskit_alt 239 | shell> pwd 240 | /home/lapeyre/myrepos/quantum_repos/qiskit_alt 241 | 242 | # Hit backspace to return to julia repl mode. 243 | # Hit `]` to enter package management mode 244 | # And add the package `LocalRegistry` to your main environment so it is always avaiable. 245 | (@v1.7) pkg> add LocalRegistry 246 | 247 | * Change the qiskit_alt project to "develop" `ElectronicStructure` like this: 248 | 249 | # Activate the current project described `Project.toml` in the echoed directory. 250 | (@v1.7) pkg> activate . 251 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 252 | 253 | (qiskit_alt) pkg> status ElectronicStructure 254 | Status `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 255 | [f7ec468b] ElectronicStructure v0.1.4 256 | 257 | # "develop" ElectronicStructure from the url given in the registry 258 | # This clones the repo and enters the path to the clone in the qiskit_alt project. 259 | (qiskit_alt) pkg> dev ElectronicStructure 260 | Cloning git-repo `https://github.com/Qiskit-Extensions/ElectronicStructure.jl.git` 261 | Resolving package versions... 262 | Updating `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 263 | [f7ec468b] ~ ElectronicStructure v0.1.4 ⇒ v0.1.4 `~/.julia/dev/ElectronicStructure` 264 | Updating `~/myrepos/quantum_repos/qiskit_alt/Manifest.toml` 265 | [f7ec468b] ~ ElectronicStructure v0.1.4 ⇒ v0.1.4 `~/.julia/dev/ElectronicStructure` 266 | ``` 267 | 268 | Now, start `qiskit_alt`. The following method automatically avoids loading 269 | any custom sytem image in `./sys_image`, which is necessary. It also probably avoids 270 | the julia installation found by the `julia_project` package and the julia exectuable found 271 | on your path. 272 | ``` 273 | In [1]: %config JuliaMagics.revise = True 274 | 275 | In [2]: %load_ext julia.magic 276 | Initializing Julia interpreter. This may take some time... 277 | 278 | In [3]: import qiskit_alt 279 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 280 | 281 | In [4]: from qiskit_alt import ElectronicStructure 282 | 283 | # In `~/.julia/dev/ElectronicStructure/src/ElectronicStructure.jl` add a line `foo = 1` and save the file. 284 | # `qiskit_alt` sees the change 285 | 286 | In [5]: ElectronicStructure.foo 287 | Out[5]: 1 288 | 289 | # Change the line to `foo = 2` and save, and see the change reflected 290 | 291 | In [6]: ElectronicStructure.foo 292 | Out[6]: 2 293 | ``` 294 | 295 | * Make the changes you want to `~/.julia/dev/ElectronicStructure/`; we will just leave `foo = 2`. 296 | * In the `Project.toml` file in `~/.julia/dev/ElectronicStructure/` we bump the version to `v0.1.5`. 297 | * Commit the changes to `ElectronicStructure`. 298 | * push the commit to `ElectronicStructure` upstream. Make a PR if you are working from a fork. 299 | * you can tag the commit, and/or make a github release, but the Julia package manager does not care. 300 | * Return to the Julia session, or start a new one, and do (after activating if needed) 301 | ```julia 302 | julia> using LocalRegistry 303 | 304 | julia> register("ElectronicStructure", registry="QuantumRegistry") 305 | ``` 306 | This should register the new version v0.1.5 in the local copy of `QuantumRegistry`, 307 | commit the changes, and push the registry upstream. If you are working from a fork of 308 | the registry, you may need to open a PR to the main repo. 309 | 310 | * free `ElectronicStructure`. 311 | ```julia 312 | (qiskit_alt) pkg> free ElectronicStructure 313 | Resolving package versions... 314 | Updating git-repo `https://github.com/Qiskit-Extensions/ElectronicStructure.jl.git` 315 | Installed ElectronicStructure ─ v0.1.5 316 | Updating `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 317 | [f7ec468b] ~ ElectronicStructure v0.1.4 `~/.julia/dev/ElectronicStructure` ⇒ v0.1.5 318 | Updating `~/myrepos/quantum_repos/qiskit_alt/Manifest.toml` 319 | [f7ec468b] ~ ElectronicStructure v0.1.4 `~/.julia/dev/ElectronicStructure` ⇒ v0.1.5 320 | Precompiling project... 321 | 4 dependencies successfully precompiled in 10 seconds (98 already precompiled) 322 | 323 | (qiskit_alt) pkg> status ElectronicStructure 324 | Status `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 325 | [f7ec468b] ElectronicStructure v0.1.5 326 | ``` 327 | This should remove the reference to our local clone from `Manifest.toml`. And download the new version 328 | of `ElectronicStructure` that we just pushed from the location given in `QuantumRegistry`. 329 | If necessary, you may want change `Project.toml` in the qiskit_alt top level to require 330 | the new version of `ElectronicStructure`. 331 | 332 | * Delete `./sys_image/sys_qiskit_alt.so` (or similar name) if it exists; it is out of date. 333 | * Start ipython and verify the changes. 334 | ```python 335 | In [1]: from qiskit_alt import ElectronicStructure 336 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 337 | 338 | In [2]: ElectronicStructure.foo 339 | Out[2]: 2 340 | ``` 341 | * Run `qiskit_alt.project.compile()` if you like to generate a new system image. 342 | 343 | Alternatively, you can do all of the steps above from `ipython`. For example (with output edited) 344 | ```python 345 | In [1]: import qiskit_alt 346 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 347 | 348 | In [2]: from qiskit_alt import julia 349 | 350 | In [3]: from julia import Pkg 351 | 352 | In [4]: Pkg.status() 353 | Status `~/myrepos/quantum_repos/qiskit_alt/Project.toml` 354 | [f7ec468b] ElectronicStructure v0.1.5 `~/quantum_repos/ElectronicStructure` 355 | [438e738f] PyCall v1.92.5 356 | [8d55b643] QiskitQuantumInfo v0.1.0 357 | [d0cc4389] QuantumOps v0.1.1 358 | [295af30f] Revise v3.1.20 359 | [8603256b] ZChop v0.3.10 360 | 361 | In [5]: Pkg.activate() # activate your main Julia environment 362 | Activating project at `~/.julia/environments/v1.7` 363 | 364 | In [6]: Pkg.status() 365 | Status `~/.julia/environments/v1.7/Project.toml` 366 | [6e4b80f9] BenchmarkTools v1.0.0 367 | [861a8166] Combinatorics v1.0.2 368 | ... 369 | 370 | In [7]: Pkg.add("LocalRegistry") # add LocalRegsitry to the main environment 371 | Updating registry at `~/.julia/registries/QuantumRegistry` 372 | Updating git-repo `https://github.com/Qiskit-Extensions/QuantumRegistry.git` 373 | Updating registry at `~/.julia/registries/General.toml` 374 | Resolving package versions... 375 | No Changes to `~/.julia/environments/v1.7/Project.toml` 376 | No Changes to `~/.julia/environments/v1.7/Manifest.toml` 377 | 378 | In [9]: Pkg.activate(".") # activate qiskit_alt environment again 379 | Activating project at `~/myrepos/quantum_repos/qiskit_alt` 380 | 381 | # Here we use a previously cloned copy rather than the default location `~/.julia/dev` 382 | In [10]: Pkg.develop(path="/home/lapeyre/quantum_repos/ElectronicStructure") 383 | 384 | In [13]: from julia import LocalRegistry 385 | 386 | # Register a package version 387 | In [14]: LocalRegistry.register("ElectronicStructure", registry="QuantumRegistry") 388 | 389 | # Return to using the version in the registries 390 | In [15]: Pkg.free("ElectronicStructure") 391 | ``` 392 | 393 | ### Making a subdirectory for examples 394 | 395 | You may want to include a subdirectory for examples or tutorials. Or you may want them in a separate repo. 396 | A new Julia environment should be defined in a `Project.toml` file. That is, you have files 397 | `./examples/ex1.py`, etc. and also `./examples/Project.toml`. 398 | You can create `Project.toml` by cd'ing into `./examples` and doing `Pkg.activate(".")`. 399 | You populate the project by doing `Pkg.add("somepackage")`. 400 | When you run the examples, activate the project somehow, from Julia or Python. 401 | By default, only the packages in your main user-wide project, e.g. `@v1.7` and 402 | those in `./examples/Project.toml` will be available. 403 | If you want to develop the examples together with the main package, say `qiskit_alt` (or others), 404 | then "develop" that package: Activate the examples project, then do `Pkg.develop(path="/path/to/dev/qiskit_alt")`. 405 | 406 | ### Compiling a system image 407 | 408 | * The code is "exercised" during compilation by running the test suites of some of the included packages. Code paths 409 | that are exercised during compilation will suffer no delay in the future, just like statically compiled libraries. 410 | More test suites and exercise scripts can be included in the compilation. 411 | And more Julia code can be moved from `qiskit_alt` into compiled modules. 412 | 413 | * "compilation" has different meanings in Julia. Code is always precompiled and cached in a `.ji` file. 414 | What happens during precompilation is described [here](https://julialang.org/blog/2021/01/precompile_tutorial/). 415 | But, this is not the kind of compilation we are considering here. 416 | 417 | 418 | ### Notes 419 | 420 | * We sometimes use this incantation at the top of Julia code `ENV["PYCALL_JL_RUNTIME_PYTHON"] = Sys.which("python")` to get the correct python 421 | interpreter. Note that the built-in Julia shell and Julia `Sys` report different paths for python. In particular the Julia shell 422 | does not inherit the path from the system shell from which Julia was invoked. 423 | ```julia 424 | shell> type python # This is the Julia repl shell 425 | python is /usr/sbin/python 426 | 427 | shell> which python 428 | /usr/sbin/python 429 | 430 | julia> Sys.which("python") 431 | "/home/username/myrepos/quantum_repos/qiskit_alt/env/bin/python" 432 | ``` 433 | 434 | * One way to enable Julia threads (on linux, and maybe other platforms) is by setting an environment variable. 435 | For example `export JULIA_NUM_THREADS=4`. You can check that the threads are available like this. 436 | ```python 437 | In [1]: from qiskit_alt import Main # You could just as well import `Base` 438 | 439 | In [2]: Main.Threads.nthreads() 440 | Out[2]: 12 441 | ``` 442 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # I used fedora:34 rather than fedora:35 as the base image because of 2 | # https://bugzilla.redhat.com/show_bug.cgi?id=1988199. 3 | # Comment 26 there points to an upstream RHEL bug, but I am not 4 | # certain it is the same bug as I was experiencing [garrison]. 5 | FROM fedora:34 6 | 7 | RUN dnf install -y git @development-tools gcc-c++ python3 python3-devel julia 8 | RUN python3 -m pip install -U pip 9 | 10 | WORKDIR /qiskit_alt 11 | 12 | COPY . . 13 | RUN pip install -e . 14 | 15 | ENV QISKIT_ALT_COMPILE=y 16 | ENV QISKIT_ALT_DEPOT=n 17 | 18 | RUN python3 -c "import qiskit_alt; qiskit_alt.project.ensure_init()" 19 | RUN python3 -c "import qiskit_alt; qiskit_alt.project.compile()" 20 | -------------------------------------------------------------------------------- /Install_Julia.md: -------------------------------------------------------------------------------- 1 | # Automatically installing Julia 2 | 3 | `qiskit_alt` will query to download and install Julia if it can't find the exectuable. The search and installation for Julia 4 | is done by the Python package [`find_julia, described here`](https://github.com/jlapeyre/find_julia). 5 | 6 | 7 | Alternatively, you can install Julia as described below. 8 | 9 | ## Installing Julia manually 10 | 11 | You can skip everything below if you allow `qiskit_alt` to install Julia for you. 12 | 13 | * Install Julia 14 | * The [Julia installer `jill.py`](https://github.com/johnnychen94/jill.py) works for most common platforms. `pip install jill`, then `jill install`. 15 | This [table](https://github.com/johnnychen94/jill.py#about-installation-and-symlink-directories) shows where jill installs 16 | and symlinks julia on various platforms. 17 | * [juliaup](https://github.com/JuliaLang/juliaup) for MSWin uses the Windows store. It also works (with improving support) for linux and macos. 18 | * Download a [prebuilt Julia distribution](https://julialang.org/downloads/) 19 | 20 | * To allow `qiskit_alt` to find the julia executable you can do one of 21 | * Install Julia with [`jill.py`](https://github.com/johnnychen94/jill.py), 22 | or [juliaup](https://github.com/JuliaLang/juliaup) and `qiskit_alt` will find it, even if it is not in your PATH. 23 | * Ensure that the julia executable is in your `PATH` environment variable. For example, under 24 | linux, `jill` makes a symlink to `/home/username/.local/bin/julia`. 25 | [More information is here](https://julialang.org/downloads/platform/). 26 | * Set the environment variable `QISKIT_ALT_JULIA_PATH` to the path of your julia executable. 27 | * Pass the option `julia_path="/path/to/julia/executable"` to the `ensure_init()` when initializing. 28 | That is: `qiskit_alt.project.ensure_init(julia_path="/path/to/julia/executable", otheopts...)` 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 IBM and its contributors 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2017 IBM and its contributors. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/qiskit_alt/sys_image/*.jl 2 | include src/qiskit_alt/sys_image/Project.toml 3 | include src/qiskit_alt/Project.toml 4 | -------------------------------------------------------------------------------- /Motivations.md: -------------------------------------------------------------------------------- 1 | * [Motivations](#motivations) 2 | * [The Problem with Python](#the-problem-with-python) a case study 3 | * [Dynamic Python Julia interface](#dynamic-python-julia-interface) 4 | * [Caveats](#caveats) 5 | 6 | ## Motivations 7 | 8 | I judge it highly probable that Julia provides a uniquely high-productivity environment for developing high-performance 9 | Qiskit features. By high-productivity, I mean higher than Python. By high-performance, I mean 10 | competitive with C or Rust. I base the judgment both on the nature of Julia and of Qiskit. In particular, 11 | Qiskit is built around a large number of complicated pure-Python types, rather than thin wrappers around 12 | standard numerical code. In the following, I present a selection of individual pieces of evidence to back up the judgment, 13 | omitting some of the most important because they require a longer discussion. 14 | However, the overall argument is inevitably rather polemical. The only way to test the judgment is through 15 | experiments, such as this package. 16 | 17 | * Julia is developed largely by applied mathematicians. It is dedicated to correct, performant, technical, computing. 18 | But the developers are committed to providing a complete ecosystem, as expected of any ambitious modern language. 19 | A successful full-stack [web framework](https://github.com/GenieFramework/Genie.jl) 20 | is written in Julia. 21 | 22 | * Julia is a dynamic language. It is as well suited as Python, I would argue better suited, for rapidly 23 | exploring ideas. This project shows that experimenting with writing Qiskit functionality in Julia, 24 | and exposing it via a Python package, can be done quickly and fruitfully. 25 | 26 | * Idiomatic, straightforward, Julia code executes as fast as statically compiled languages. 27 | In addition Julia offers many opportunities for further optimization without leaving the language; 28 | For instance, using macros such as `@inbounds`, `@simd`, `@avx`, `@threads`, and specialized data 29 | types. 30 | 31 | * Native data structures are the same or similar to those in languages such as C++ and Rust. 32 | * Ideas developed in Julia may be ported to these languages and vice-versa. For instance, to 33 | Qiskit modules written in C++ or Rust. 34 | * Interfaces between Julia and these languages are straightforward and highly efficient. 35 | 36 | * Julia is fully committed to a single, coherent, type system in all aspects of the language. 37 | 38 | ### The Problem with Python 39 | 40 | A case study 41 | 42 | * A large amount of Qiskit development effort is expended working around the fact that Python lacks the 43 | features above. An example is the following sequence (and several issues linked within). The issue 44 | involved trying to write both efficient and generic code in a hot location. 45 | * [Using a numpy integer type as an index for a QuantumRegister fails #3929](https://github.com/Qiskit/qiskit-terra/issues/3929) 46 | 16 comments 47 | * [Allow numbers.Integral instead of only built-in int for QuantumRegister index #4591](https://github.com/Qiskit/qiskit-terra/pull/4591) 48 | 25 comments, 25 commits. 49 | * [Performance regression in circuit construction because of type checking #4791](https://github.com/Qiskit/qiskit-terra/issues/4791) 50 | 3 comments 51 | * [Fixed #4791: Explicity checked for several int types #4810](https://github.com/Qiskit/qiskit-terra/pull/4810) 52 | 1 comment 53 | 54 | The solution was to enumerate possible types and take a small performance hit. Presumably, if further numpy derivatives 55 | need to be supported, we could weigh the benefit of supporting them vs the cost of adding more checks to the list. 56 | In contrast, in Julia, 57 | * Regarding different types of integers, `Int64`, `Int32`, etc.; one would, idiomatically, with little consideration, 58 | use a parametric type, possibly constrained by the supertype `Integer`. 59 | * Standard Julia arrays and all kinds of exotic arrays use the same set of integer types, so the issue of supporting 60 | different numerical packages, GPUs, AD, etc. in this context would not arise. 61 | * because of compilation and inlining, the check would take, rather than microseconds, strictly no time; it would be elided. 62 | * So, compared to the Python solution, the code would be far more generic, take far less engineering effort (none), and be 63 | far more efficient. 64 | 65 | I see similar issues arise over and over in Qiskit development. The productivity gain in developing Qiskit algorithms 66 | in Julia rather than Python would be, by my rough, not-quite-semi-quantitative, estimate, about ten times. I mean 67 | medium scale development. Larger than the Jordan-Wigner implementation below, but less than reproducing all of Qiskit. 68 | It would be interesting, but very difficult, to try to support this estimate with evidence. I think a better approach 69 | is to carry out experiments such as qiskit_alt. 70 | 71 | ### Dynamic Python Julia interface 72 | 73 | * There are a few good options for using Python and Julia together. The approach here uses 74 | pyjulia, which offers the Python module `julia`. This allows mixing Julia and Python modules 75 | dynamically and rapidly with no interface code required. Conversions of data types is handled 76 | by pyjulia. You can call existing Julia modules or define them from Python. For example, 77 | ```python 78 | In [1] from julia import Main 79 | In [2]: Main.eval(""" 80 | ...: module Foo 81 | ...: calc(array, num) = array .+ num 82 | ...: end"""); 83 | In [3]: Main.Foo.calc([1, 2, 3], 4) 84 | Out[3]: array([5, 6, 7], dtype=int64) 85 | ``` 86 | Here, we have defined a function `calc` in a Julia module `Foo`. 87 | The input Python list is converted to a Julia array. And the returned Julia array is converted to 88 | a numpy array. Here is an example of how we convert a user defined type from Julia to Python. 89 | ```python 90 | def jlPauliList(pauli_list): 91 | """ 92 | Convert a QiskitQuantumInfo.PauliList to a qiskit.quantum_info.PauliList 93 | """ 94 | return PauliList.from_symplectic(pauli_list.z, pauli_list.x) 95 | ``` 96 | 97 | ### Caveats 98 | 99 | * Julia is neither purely interpreted nor traditionally statically compiled. What it "is" evolves; 100 | just-ahead-of-time compiled is a useful description. 101 | But, it doesn't have all the advantages of a language that is largely committed to one (Python) or the other (Rust) model. 102 | In the past, the JIT (or JAOT) penalty was typically large and there weren't a lot of good ways to mitigate it. 103 | The situation today is much, much, improved on several fronts. But, it is still an issue. 104 | At the moment 105 | * The JIT penalty for qiskit_alt is not great (for someone who started using Julia in 2015). 106 | First, about 8 seconds to import `qiskit_alt`. Then there are various delays 107 | for compiling code paths. There are large Julia dependencies that might be attractive, but that incur larger JIT penalties. One 108 | would have to weigh the consequences of adopting them. 109 | * But, the penalty is not static, rather it is improving. Ongoing developments include switching to the interpreter; 110 | more fine-grained optimization and code specialization levels; techniques to avoid cache-invalidations in the method table, 111 | a huge source of compilation cost. 112 | * One can reduce the start-up penalty by loading on demand. The [current system](https://github.com/JuliaPackaging/Requires.jl) 113 | is ok, but a bit ad-hoc and limited. There is talk about improving it, which should require only ordinary engineering effort. 114 | In fact, we use it to get around a hard dependency on `pyscf`, but I will likely make `pyscf` a hard dependency. 115 | * One can make a fully AOT-compiled system image. Currently, we can build this locally in `qiskit_alt` in less than five minutes. 116 | The basic tooling is there, and improving, but could be more polished. 117 | We are rolling-our own functions and scripts. How robust it is, is a crucial 118 | question. During development, currently, you *cannot* use the system image. The compiled-in package cannot be replaced dynamically. 119 | For development, we instead rely on [Revise](./Development.md). 120 | 121 | * Julia is not magic, performance-pixie-dust. This should go without saying, but the misconception is encountered frequently enough to 122 | be dangerous to projects. I have seen a relatively small amount of not-to-obscure numba code easily outperform a Julia package. 123 | Using Julia effectively requires learning 124 | some [basic guidelines](https://docs.julialang.org/en/v1/manual/performance-tips/). They are not really difficult to learn and 125 | to employ, but are not optional. So, a good Julia project requires a certain amount of Julia culture. Is it more than that required 126 | for a quality Python project? I think likely not, but it is an important question. 127 | 128 | * Julia does not (yet) have multiple inheritance. More generally, understanding how to best use multiple dispatch, traits, various macro-based embellishments 129 | to the type system is perhaps not as mature as the understanding of design in Python. 130 | However, some of the [most ancient (9 years old), large packages](https://github.com/JuliaStats/Distributions.jl) 131 | have weathered the changes relatively well and are still widely used. 132 | 133 | * The key creator of Julia, Jeff Bezanson, can tell you [What's bad about Julia](https://www.youtube.com/watch?v=TPuJsgyu87U) in 2019. 134 | This covers the points above and many others. 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qiskit_alt 2 | 3 | ## qiskit_alt 4 | 5 | This Python package uses a backend written in Julia to implement high performance features for 6 | standard Qiskit. This package is a proof of concept with little high-level code. 7 | 8 | Installing and managing Julia and its packages is automated. So you don't need to learn anything about Julia 9 | to get started. 10 | 11 | ### Table of contents 12 | 13 | * [Installation and configuration notes](#installation-and-configuration-notes) 14 | 15 | * [Compilation](#compilation) Compiling a system image to eliminate compilation at run time. 16 | 17 | * [Using qiskit_alt](#using-qiskit_alt) First steps. 18 | 19 | * [Manual Steps](#manual-steps) Details of automatic installation. 20 | 21 | * [Introduction](#introduction) 22 | 23 | * [Motivations](./Motivations.md) 24 | 25 | * [Demonstration](#demonstration) 26 | * [Zapata demo of Jordan-Wigner transformation in Julia](https://www.youtube.com/watch?v=-6VfSgPXe4s&list=PLP8iPy9hna6Tl2UHTrm4jnIYrLkIcAROR); The 27 | same thing as the main demonstration in qiskit_alt. 28 | 29 | * [Julia Packages](#julia-packages) Julia packages that qiskit_alt depends on. 30 | 31 | * [Troubleshooting](#troubleshooting) 32 | 33 | * [Development](./Development.md). Instructions for developing qiskit_alt. 34 | 35 | ## Installation and Configuration Notes 36 | 37 | ### Basic 38 | 39 | * `qiskit_alt` is available on pypi 40 | 41 | ```shell 42 | shell> pip install qiskit_alt 43 | ``` 44 | 45 | * Complete installation by running 46 | ```python 47 | import qiskit_alt 48 | qiskit_alt.project.ensure_init() 49 | ``` 50 | 51 | See [`julia_project`](https://github.com/jlapeyre/julia_project) for more options. 52 | 53 | * If no Julia executable is found, `jill.py` will be used to download and install it. It is *not* necessary 54 | to add the installation path or symlink path to your search PATH to use julia from qiskit_alt. 55 | Before offering to install Julia, `qiskit_alt` will search for julia as [described here](./Install_Julia.md). 56 | 57 | * The Julia packages are installed the first time you run `qiskit_alt.project.ensure_init()` from Python. If this fails, 58 | see the log file qiskit_alt.log. You can open a bug report in the [`qiskit_alt` repo](https://github.com/Qiskit-Extensions/qiskit-alt) 59 | 60 | * Check that the installation is not completely broken by running benchmark scripts, with the string "alt" in the name: 61 | ```sh 62 | python ./bench/run_all_bench.py 63 | ``` 64 | Note that the folder `bench` is not included when you install via `pip install qiskit_alt`. 65 | But it can be [downloaded here](./bench/). 66 | 67 | 68 | ### More installation details 69 | 70 | * `qiskit_alt` depends on [`pyjulia`](https://pyjulia.readthedocs.io/en/latest/index.html) 71 | and/or [`juliacall`](https://github.com/cjdoris/PythonCall.jl) for communication between Julia and Python. 72 | 73 | 74 | * `pyjulia` and `juliacall` are two packages for communication between Python and Julia. You only need 75 | to import one of them. But, you won't import them directly. 76 | 77 | * When you initialize with `qiskit_alt.project.ensure_init()` the default communication package is chosen. 78 | You can also choose explicitly with `qiskit_alt.project.ensure_init(calljulia="pyjulia")` 79 | or `qiskit_alt.project.ensure_init(calljulia="juliacall")` 80 | 81 | * The installation is interactive. How to do a non-interactive installation with environment variables is 82 | described below. 83 | 84 | * You may allow `qiskit_alt` to download and install Julia for you, using [`jill.py`](https://github.com/johnnychen94/jill.py). 85 | Otherwise you can follow instructions for [installing Julia with an installation tool](./Install_Julia.md). 86 | 87 | * We recommend using a virtual Python environment with `venv` or `conda`. For example `python -m venv ./env`, 88 | which creates a virtual environment for python packages needed to run `qiskit_alt`. 89 | You can use whatever name you like in place of the directory `./env`. 90 | 91 | * Activate the environment using the file required for your shell. For example 92 | `source ./env/bin/activate` for `venv` and bash. 93 | 94 | * Install `qiskit_alt` with `pip install qiskit_alt`. 95 | 96 | * Install whatever other packages you want. For example `pip install ipython`. 97 | 98 | * Configuring installation with environment variables. If you set these environment variables, you will not be prompted 99 | during installation. 100 | * Set `QISKIT_ALT_JULIA_PATH` to the path to a Julia executable (in a Julia installation). This variable takes 101 | precedence over other methods of specifying the path to the executable. 102 | * Set `QISKIT_ALT_INSTALL_JULIA` to `y` or `n` to confirm or disallow installing julia via `jill.py`. 103 | * Set `QISKIT_ALT_COMPILE` to `y` or `n` to confirm or disallow compiling a system image after installing Julia packages 104 | * Set `QISKIT_ALT_DEPOT` to `y` or `n` to force using or not using a Julia "depot" specific to this project. 105 | 106 | * `qiskit_alt.project.update()` will delete `Manifest.toml` files; upgrade packages; rebuild the manifest; delete compiled system images. 107 | If you call `update()` while running a compiled system image, you should exit Python and start again before compiling 108 | 109 | * `qiskit_alt.project` is an instance of `JuliaProject` from the package [`julia_project`](https://github.com/jlapeyre/julia_project). 110 | for managing Julia dependencies in Python projects. See more options at [`julia_project`](https://github.com/jlapeyre/julia_project). 111 | 112 | ### Compilation 113 | 114 | * We highly recommend compiling a system image for `qiskit_alt` to speed up loading and reduce delays due to just-in-time compilation. 115 | You will be prompted to install when installing or upgrading. Compilation may also be done at any time as follows. 116 | 117 | ```python 118 | [1]: import qiskit_alt 119 | 120 | In [2]: qiskit_alt.project.ensure_init(use_sys_image=False) 121 | 122 | In [3]: qiskit_alt.project.compile() 123 | ``` 124 | Compilation takes about four minutes. The new Julia system image will be found the next time you import `qiskit_alt`. 125 | Note that we disabled possibly loading a previously-compiled system image before compiling a new one. 126 | This avoids some possible stability issues. 127 | 128 | 129 | ## Using qiskit_alt 130 | 131 | This is a very brief introduction. 132 | 133 | * The `pyjulia` interface is exposed via the `julia` module. The `juliacall` module is called `juliacall`. 134 | However you should *not* do `import julia` or `import juliacall` before `import qiskit_alt`, 135 | and `qiskit_alt.project.ensure_init()` (or `qiskit_alt.project.ensure_init(calljulia="pyjulia")` or 136 | `juliacall` with `qiskit_alt.project.ensure_init(calljulia="juliacall")`) 137 | This is because `import julia` will circumvent the facilities described above for choosing the julia executable and the 138 | compiled system image. 139 | 140 | 141 | * Julia modules are loaded like this. Note that `qiskit_alt.project.julia` points to either `julia` or `juliacall` depending 142 | on which was chosen. 143 | ```python 144 | import qiskit_alt 145 | qiskit_alt.project.ensure_init(calljulia=interface_choice) 146 | Main = qiskit_alt.project.julia.Main 147 | ``` 148 | 149 | `import qiskit_alt`; `import julia`; `from julia import PkgName`. 150 | After this, all functions and symbols in `PkgName` are available. 151 | You can do, for example 152 | ```python 153 | In [1]: import qiskit_alt 154 | 155 | In [2]: qiskit_alt.project.ensure_init() 156 | 157 | In [3]: julia = qiskit_alt.project.julia 158 | 159 | In [4]: julia.Main.cosd(90) 160 | Out[4]: 0.0 161 | 162 | In [5]: QuantumOps = qiskit_alt.project.simple_import("QuantumOps") 163 | 164 | In [6]: pauli_sum = QuantumOps.rand_op_sum(QuantumOps.Pauli, 3, 4); pauli_sum 165 | Out[6]: 166 | 171 | ``` 172 | 173 | In the last example above, `PauliSum` is a Julia object. The `PauliSum` can be converted to 174 | a Qiskit `SparsePauliOp` like this. 175 | ```python 176 | In [7]: from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp 177 | 178 | In [8]: PauliSum_to_SparsePauliOp(pauli_sum) 179 | Out[8]: 180 | SparsePauliOp(['ZII', 'IYX', 'XIY', 'ZIZ'], 181 | coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j]) 182 | ``` 183 | 184 | ## Introduction 185 | 186 | The highlights thus far are in [benchmark code](./bench/), which is 187 | presented in the demonstration notebooks. 188 | There is one [demonstration notebook using `pyjulia`](./demos/qiskit_alt_demo.ipynb) 189 | and another [demonstration notebook using `juliacall`](./demos/qiskit_alt_demo_jc.ipynb). 190 | 191 | The main application-level demonstration is computing a qubit Hamiltonian as a `qiskit.quantum_info.SparsePauliOp` 192 | from a Python list specifiying the molecule geometry in the same format as that used by `qiskit_nature`. 193 | 194 | * The Jordan-Wigner transform in qiskit_alt is 30 or so times faster than in qiskit-nature. 195 | * Computing a Fermionic Hamiltonian from pyscf integrals is several times faster, with the factor increasing 196 | with the problem size. 197 | * Converting an operator from the computational basis, as a numpy matrix, to the Pauli basis, as a `qiskit.quantum_info.SparsePauliOp`, 198 | is many times faster with the factor increasing rapidly in the number of qubits. 199 | 200 | You might want to skip to [installation instructions](#installation-and-configuration-notes) 201 | 202 | 203 | 204 | ## Demonstration 205 | 206 | * There are a few demos in this [demonstration benchmark notebook](./demos/qiskit_alt_demo.ipynb) 207 | 208 | * The [benchmark code](./bench/) is a good place to get an idea of what qiskit_alt can do. 209 | 210 | * Here are [some demonstration notebooks](https://github.com/Qiskit-Extensions/QuantumOpsDemos) of the Julia packages behind `qiskit_alt`. 211 | 212 | * [Zapata demo of Jordan-Wigner transformation in Julia](https://www.youtube.com/watch?v=-6VfSgPXe4s&list=PLP8iPy9hna6Tl2UHTrm4jnIYrLkIcAROR); The 213 | same thing as the main demonstration in qiskit_alt. This is from JuliaCon 2020. 214 | 215 | 216 | ### Managing Julia packages 217 | 218 | * Available Julia modules are those in the standard library and those listed in [Project.toml](./Project.toml). 219 | You can add more packages (and record them in `Project.toml`) by doing `qiskit_alt.project.julia.Pkg.add("PackageName")`. 220 | You can also do the same by avoiding Python and using the julia cli. 221 | 222 | 223 | ## Julia Packages 224 | 225 | * The Julia repos [`QuantumOps.jl`](https://github.com/Qiskit-Extensions/QuantumOps.jl) and 226 | [`ElectronicStructure.jl`](https://github.com/Qiskit-Extensions/ElectronicStructure.jl) and 227 | [`QiskitQuantumInfo.jl`](https://github.com/Qiskit-Extensions/QiskitQuantumInfo.jl) 228 | are not registered in the General Registry, but rather in [`QuantumRegistry`](https://github.com/Qiskit-Extensions/QuantumRegistry) which contains just 229 | a handful of packages for this project. 230 | 231 | ## Testing 232 | 233 | The test folder is mostly out of date. 234 | 235 | #### Testing installation with docker 236 | 237 | See [the readme](./docker_tests/README-docker_tests.md). 238 | 239 | 244 | 245 | 247 | 249 | 251 | 253 | 255 | 257 | 259 | 261 | 263 | 265 | 267 | -------------------------------------------------------------------------------- /_setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = {} 4 | with open("./qiskit_alt/_version.py") as fp: 5 | exec(fp.read(), version) 6 | 7 | setup( 8 | name='qiskit_alt', 9 | version=version['__version__'], 10 | description='Alternative to parts of qiskit, written in Julia', 11 | url='https://github.com/Qiskit-Extensions/qiskit-alt', 12 | author='John Lapeyre', 13 | packages=find_packages(), 14 | install_requires=[ 15 | 'pyscf>=2.0.1', 16 | 'julia>=0.5.7', 17 | 'juliacall', 18 | 'qiskit-terra>=0.19.0', 19 | 'qiskit-nature>=0.2.2', 20 | 'julia_project>=0.1.6' 21 | ], 22 | include_package_data=True 23 | ) 24 | -------------------------------------------------------------------------------- /bench/fermionic_alt_time.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit_alt constructing Fermionic operators from pyscf integrals. 2 | import qiskit_alt 3 | qiskit_alt.project.ensure_init() 4 | 5 | import timeit 6 | 7 | def make_setup_code(basis, geometry): 8 | return f""" 9 | import qiskit_alt 10 | 11 | h2_geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.7414]]] 12 | 13 | h2o_geometry = [['O', [0., 0., 0.]], 14 | ['H', [0.757, 0.586, 0.]], 15 | ['H', [-0.757, 0.586, 0.]]] 16 | 17 | from qiskit_alt.electronic_structure import fermionic_hamiltonian 18 | fermionic_hamiltonian({geometry}, {basis}) 19 | #qiskit_alt.fermionic_hamiltonian({geometry}, {basis}) 20 | """ 21 | 22 | def run_one_basis(basis, geometry, num_repetitions): 23 | setup_code = make_setup_code(basis, geometry) 24 | bench_code = f"fermionic_hamiltonian({geometry}, {basis})" 25 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 26 | t = 1000 * time / num_repetitions 27 | print(f"geometry={geometry}, basis={basis} {t:0.2f}", "ms") 28 | return t 29 | 30 | 31 | def run_benchmarks(): 32 | alt_times = [] 33 | 34 | for basis, geometry, num_repetitions in (("'sto3g'", "h2_geometry", 10), ("'631g'", "h2_geometry", 10), 35 | ("'631++g'", "h2_geometry", 10), 36 | ("'sto3g'", "h2o_geometry", 10), ("'631g'", "h2o_geometry", 5)): 37 | t = run_one_basis(basis, geometry, num_repetitions) 38 | alt_times.append(t) 39 | 40 | return alt_times 41 | 42 | 43 | if __name__ == '__main__': 44 | alt_times = run_benchmarks() 45 | -------------------------------------------------------------------------------- /bench/fermionic_nature_time.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit-nature constructing Fermionic operators from pyscf integrals. 2 | import timeit 3 | 4 | def make_setup_code(basis, geometry): 5 | return f""" 6 | from qiskit_nature.drivers import UnitsType, Molecule 7 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriverType, ElectronicStructureMoleculeDriver 8 | 9 | h2_geometry = [['H', [0., 0., 0.]], 10 | ['H', [0., 0., 0.735]]] 11 | 12 | h2o_geometry = [['O', [0., 0., 0.]], 13 | ['H', [0.757, 0.586, 0.]], 14 | ['H', [-0.757, 0.586, 0.]]] 15 | 16 | basis = {basis} 17 | 18 | molecule = Molecule(geometry={geometry}, 19 | charge=0, multiplicity=1) 20 | driver = ElectronicStructureMoleculeDriver(molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF) 21 | 22 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 23 | from qiskit_nature.converters.second_quantization import QubitConverter 24 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 25 | 26 | es_problem = ElectronicStructureProblem(driver) 27 | """ 28 | 29 | def run_one_basis(basis, geometry, num_repetitions): 30 | setup_code = make_setup_code(basis, geometry) 31 | bench_code = "es_problem.second_q_ops()" 32 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 33 | t = 1000 * time / num_repetitions 34 | print(f"geometry={geometry}, basis={basis} {t:0.2f}", "ms") 35 | return t 36 | 37 | 38 | def run_benchmarks(): 39 | nature_times = [] 40 | 41 | for basis, geometry, num_repetitions in (("'sto3g'", "h2_geometry", 10), ("'631g'", "h2_geometry", 10), 42 | ("'631++g'", "h2_geometry", 5), 43 | ("'sto3g'", "h2o_geometry", 5), ("'631g'", "h2o_geometry", 1)): 44 | t = run_one_basis(basis, geometry, num_repetitions) 45 | nature_times.append(t) 46 | return nature_times 47 | 48 | 49 | if __name__ == '__main__': 50 | nature_times = run_benchmarks() 51 | -------------------------------------------------------------------------------- /bench/from_matrix_alt.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit_alt transforming an operator from the computational- to the Pauli basis. 2 | import qiskit_alt 3 | qiskit_alt.project.ensure_init() 4 | 5 | import timeit 6 | 7 | Main = qiskit_alt.project.julia.Main 8 | 9 | def make_setup_code(nqubits): 10 | return f""" 11 | import qiskit_alt 12 | from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp 13 | QuantumOps = qiskit_alt.project.simple_import("QuantumOps") 14 | import numpy as np 15 | Main = qiskit_alt.project.julia.Main 16 | 17 | m = np.random.rand(2**{nqubits}, 2**{nqubits}) 18 | """ 19 | 20 | def run_one(nqubits, num_repetitions): 21 | setup_code = make_setup_code(nqubits) 22 | if qiskit_alt.project._calljulia_name == 'juliacall': 23 | bench_code = "PauliSum_to_SparsePauliOp(QuantumOps.PauliSum(Main.convert(Main.Matrix, m)))" 24 | else: 25 | bench_code = "PauliSum_to_SparsePauliOp(QuantumOps.PauliSum(m))" 26 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 27 | t = 1000 * time / num_repetitions 28 | print(f"nqubits={nqubits}, {t:0.2f}", "ms") 29 | return t 30 | 31 | 32 | def run_benchmarks(): 33 | qk_alt_times = [] 34 | 35 | for nqubits, num_repetitions in ((2, 50), (3, 50), (4, 10), (5, 10), (6, 10), 36 | (7, 10), 37 | (8, 3)): 38 | t = run_one(nqubits, num_repetitions) 39 | qk_alt_times.append(t) 40 | return qk_alt_times 41 | 42 | 43 | if __name__ == '__main__': 44 | qk_alt_times = run_benchmarks() 45 | -------------------------------------------------------------------------------- /bench/from_matrix_quantum_info.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit.quantum_info transforming an operator from the computational- to the Pauli basis. 2 | import timeit 3 | 4 | def make_setup_code(nqubits): 5 | return f""" 6 | from qiskit.quantum_info import SparsePauliOp 7 | from qiskit.quantum_info import Operator 8 | import numpy as np 9 | 10 | m = np.random.rand(2**{nqubits}, 2**{nqubits}) 11 | """ 12 | 13 | def run_one(nqubits, num_repetitions): 14 | setup_code = make_setup_code(nqubits) 15 | bench_code = "SparsePauliOp.from_operator(Operator(m))" 16 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 17 | t = 1000 * time / num_repetitions 18 | print(f"nqubits={nqubits}, {t:0.2f}", "ms") 19 | return t 20 | 21 | 22 | def run_benchmarks(): 23 | pyqk_times = [] 24 | 25 | for nqubits, num_repetitions in ((2, 50), (3, 50), (4, 10), (5, 10), (6, 5), 26 | (7, 1), (8, 1)): 27 | t = run_one(nqubits, num_repetitions) 28 | pyqk_times.append(t) 29 | return pyqk_times 30 | 31 | 32 | if __name__ == '__main__': 33 | pyqk_times = run_benchmarks() 34 | -------------------------------------------------------------------------------- /bench/jordan_wigner_alt_time.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit_alt peforming the Jordan-Wigner transform on a Fermi operator. 2 | import qiskit_alt 3 | qiskit_alt.project.ensure_init() 4 | 5 | import timeit 6 | 7 | def make_setup_code(basis, geometry): 8 | return f""" 9 | import qiskit_alt.electronic_structure 10 | 11 | h2_geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.7414]]] 12 | 13 | h2o_geometry = [['O', [0., 0., 0.]], 14 | ['H', [0.757, 0.586, 0.]], 15 | ['H', [-0.757, 0.586, 0.]]] 16 | 17 | basis = {basis} 18 | fermi_op = qiskit_alt.electronic_structure.fermionic_hamiltonian({geometry}, basis) 19 | qiskit_alt.electronic_structure.jordan_wigner(fermi_op); 20 | """ 21 | 22 | def run_one_basis(basis, geometry, num_repetitions): 23 | setup_code = make_setup_code(basis, geometry) 24 | bench_code = "qiskit_alt.electronic_structure.jordan_wigner(fermi_op)" 25 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 26 | t = 1000 * time / num_repetitions 27 | print(f"geometry={geometry}, basis={basis} {t:0.2f}", "ms") 28 | return t 29 | 30 | 31 | def run_benchmarks(): 32 | alt_times = [] 33 | 34 | for basis, geometry, num_repetitions in (("'sto3g'", "h2_geometry", 10), ("'631g'", "h2_geometry", 10), 35 | ("'631++g'", "h2_geometry", 10), 36 | ("'sto3g'", "h2o_geometry", 10), ("'631g'", "h2o_geometry", 5)): 37 | t = run_one_basis(basis, geometry, num_repetitions) 38 | alt_times.append(t) 39 | 40 | return alt_times 41 | 42 | 43 | if __name__ == '__main__': 44 | alt_times = run_benchmarks() 45 | -------------------------------------------------------------------------------- /bench/jordan_wigner_nature_time.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit-nature peforming the Jordan-Wigner transform on a Fermi operator. 2 | import timeit 3 | 4 | def make_setup_code(basis, geometry): 5 | return f""" 6 | from qiskit_nature.drivers import UnitsType, Molecule 7 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriverType, ElectronicStructureMoleculeDriver 8 | 9 | h2_geometry = [['H', [0., 0., 0.]], 10 | ['H', [0., 0., 0.735]]] 11 | 12 | h2o_geometry = [['O', [0., 0., 0.]], 13 | ['H', [0.757, 0.586, 0.]], 14 | ['H', [-0.757, 0.586, 0.]]] 15 | 16 | basis = {basis} 17 | 18 | molecule = Molecule(geometry={geometry}, 19 | charge=0, multiplicity=1) 20 | driver = ElectronicStructureMoleculeDriver(molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF) 21 | 22 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 23 | from qiskit_nature.converters.second_quantization import QubitConverter 24 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 25 | 26 | es_problem = ElectronicStructureProblem(driver) 27 | second_q_op = es_problem.second_q_ops() 28 | 29 | qubit_converter = QubitConverter(mapper=JordanWignerMapper()) 30 | 31 | hamiltonian = second_q_op[0] 32 | """ 33 | 34 | def run_one_basis(basis, geometry, num_repetitions): 35 | setup_code = make_setup_code(basis, geometry) 36 | bench_code = "qubit_converter.convert(hamiltonian)" 37 | time = timeit.timeit(stmt=bench_code, setup=setup_code, number=num_repetitions) 38 | t = 1000 * time / num_repetitions 39 | print(f"geometry={geometry}, basis={basis} {t:0.2f}", "ms") 40 | return t 41 | 42 | 43 | def run_benchmarks(): 44 | nature_times = [] 45 | 46 | for basis, geometry, num_repetitions in (("'sto3g'", "h2_geometry", 10), ("'631g'", "h2_geometry", 10), 47 | ("'631++g'", "h2_geometry", 5), 48 | ("'sto3g'", "h2o_geometry", 5), ("'631g'", "h2o_geometry", 1)): 49 | t = run_one_basis(basis, geometry, num_repetitions) 50 | nature_times.append(t) 51 | return nature_times 52 | 53 | 54 | if __name__ == '__main__': 55 | nature_times = run_benchmarks() 56 | -------------------------------------------------------------------------------- /bench/pauli_from_list_alt.py: -------------------------------------------------------------------------------- 1 | # Benchmark qiskit_alt creating a SparsePauliOp from a list of strings. 2 | import sys 3 | import qiskit_alt 4 | qiskit_alt.project.ensure_init() 5 | 6 | import random 7 | from timeit import timeit 8 | 9 | Main = qiskit_alt.project.julia.Main 10 | 11 | QuantumOps = qiskit_alt.project.simple_import("QuantumOps") 12 | from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp 13 | 14 | random.seed(123) 15 | 16 | def rand_label(k, n): 17 | return ["".join(random.choices("IXYZ", k=k)) for _ in range(n)] 18 | 19 | 20 | def run_benchmarks(): 21 | qkalt_times = [] 22 | 23 | for k in (10, 100): 24 | for n in (10, 100, 1000, 5000, 10_000, 100_000): 25 | label = rand_label(k, n) 26 | if qiskit_alt.project._calljulia_name == 'juliacall': 27 | label = Main.pyconvert_list(Main.String, label) 28 | PauliSum_to_SparsePauliOp(QuantumOps.PauliSum(label)) 29 | number = 20 30 | t = timeit(lambda: PauliSum_to_SparsePauliOp(QuantumOps.PauliSum(label)), number=number) 31 | t = t * 1000 / number 32 | qkalt_times.append(t) 33 | print(f'k={k}, n={n}, {t} ms') 34 | return qkalt_times 35 | 36 | 37 | if __name__ == '__main__': 38 | qkalt_times = run_benchmarks() 39 | 40 | -------------------------------------------------------------------------------- /bench/pauli_from_list_qinfo.py: -------------------------------------------------------------------------------- 1 | # Benchmark quantum_info creating a SparsePauliOp from a list of strings. 2 | import random 3 | from timeit import timeit 4 | 5 | from qiskit.quantum_info import PauliList, SparsePauliOp 6 | 7 | random.seed(123) 8 | 9 | def rand_label(k, n): 10 | return ["".join(random.choices("IXYZ", k=k)) for _ in range(n)] 11 | 12 | 13 | def run_benchmarks(): 14 | quantum_info_times = [] 15 | for k in (10, 100): 16 | for n in (10, 100, 1000, 5000, 10_000, 100_000): 17 | label = rand_label(k, n) 18 | number = 20 19 | if n >= 10_000: 20 | number = 1 21 | t = timeit(lambda: SparsePauliOp(PauliList(label)).simplify(), number=number) 22 | t = t * 1000 / number 23 | quantum_info_times.append(t) 24 | print(f'k={k}, n={n}, {t} ms') 25 | return quantum_info_times 26 | 27 | 28 | if __name__ == '__main__': 29 | quantum_info_times = run_benchmarks() 30 | -------------------------------------------------------------------------------- /bench/run_all_bench.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import shutil 3 | import os 4 | 5 | # Include these lines if we run all files in one process 6 | import qiskit_alt 7 | qiskit_alt.project.ensure_init() 8 | 9 | 10 | bench_scripts = [ 11 | "fermionic_alt_time.py", 12 | "fermionic_nature_time.py", 13 | "from_matrix_alt.py", 14 | "from_matrix_quantum_info.py", 15 | "jordan_wigner_alt_time.py", 16 | "jordan_wigner_nature_time.py", 17 | "pauli_from_list_alt.py", 18 | "pauli_from_list_qinfo.py", 19 | ] 20 | 21 | 22 | _python = shutil.which("python") 23 | 24 | ## Run each benchmark script in a separate process 25 | def run_bench(fname): 26 | dirname = os.path.dirname(os.path.abspath(__file__)) 27 | full = os.path.join(dirname, fname) 28 | res = subprocess.run( 29 | [_python, full], check=True, capture_output=True, encoding='utf8' 30 | ).stdout 31 | print(res) 32 | 33 | 34 | def exec_full_dir(fname): 35 | dirname = os.path.dirname(os.path.abspath(__file__)) 36 | filepath = os.path.join(dirname, fname) 37 | exec_full(filepath) 38 | 39 | 40 | def exec_full(filepath): 41 | global_namespace = { 42 | "__file__": filepath, 43 | "__name__": "__main__", 44 | } 45 | with open(filepath, 'rb') as file: 46 | exec(compile(file.read(), filepath, 'exec'), global_namespace) 47 | 48 | 49 | for fname in bench_scripts: 50 | print(fname) 51 | exec_full_dir(fname) 52 | print() 53 | -------------------------------------------------------------------------------- /bench/run_only_alt.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import shutil 3 | import os 4 | 5 | # Include these lines if we run all files in one process 6 | import qiskit_alt 7 | qiskit_alt.project.ensure_init() 8 | 9 | 10 | bench_scripts = [ 11 | "fermionic_alt_time.py", 12 | "from_matrix_alt.py", 13 | "jordan_wigner_alt_time.py", 14 | "pauli_from_list_alt.py" 15 | ] 16 | 17 | 18 | _python = shutil.which("python") 19 | 20 | ## Run each benchmark script in a separate process 21 | def run_bench(fname): 22 | dirname = os.path.dirname(os.path.abspath(__file__)) 23 | full = os.path.join(dirname, fname) 24 | res = subprocess.run( 25 | [_python, full], check=True, capture_output=True, encoding='utf8' 26 | ).stdout 27 | print(res) 28 | 29 | 30 | def exec_full_dir(fname): 31 | dirname = os.path.dirname(os.path.abspath(__file__)) 32 | filepath = os.path.join(dirname, fname) 33 | exec_full(filepath) 34 | 35 | 36 | def exec_full(filepath): 37 | global_namespace = { 38 | "__file__": filepath, 39 | "__name__": "__main__", 40 | } 41 | with open(filepath, 'rb') as file: 42 | exec(compile(file.read(), filepath, 'exec'), global_namespace) 43 | 44 | 45 | def run_all(): 46 | for fname in bench_scripts: 47 | print(fname) 48 | exec_full_dir(fname) 49 | print() 50 | 51 | 52 | if __name__ == '__main__': 53 | run_all() 54 | -------------------------------------------------------------------------------- /demos/Comparing-qiskit_alt-and-qiskit_nature-VQE-output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1b3a4b04", 6 | "metadata": {}, 7 | "source": [ 8 | "### Load and initialize qiskit_alt " 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "01082cac", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import qiskit_alt\n", 19 | "qiskit_alt.project.ensure_init()\n", 20 | "\n", 21 | "# The following line would also initialize qiskit_alt behind the scenes, but we have made it explicity above.\n", 22 | "# import qiskit_alt.electronic_structure" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "id": "3c6ae0b1", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "from qiskit.providers import aer # make sure this is available" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 3, 38 | "id": "635e46c9", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "from qiskit_nature.drivers import UnitsType, Molecule\n", 43 | "from qiskit_nature.drivers.second_quantization import (\n", 44 | " ElectronicStructureDriverType,\n", 45 | " ElectronicStructureMoleculeDriver,\n", 46 | ")\n", 47 | "from qiskit_nature.problems.second_quantization import ElectronicStructureProblem\n", 48 | "from qiskit_nature.converters.second_quantization import QubitConverter\n", 49 | "from qiskit_nature.mappers.second_quantization import JordanWignerMapper\n", 50 | "\n", 51 | "from qiskit.algorithms import VQE\n", 52 | "from qiskit.circuit.library import TwoLocal\n", 53 | "from qiskit.utils import QuantumInstance\n", 54 | "from qiskit import Aer\n", 55 | "from qiskit.algorithms.optimizers import COBYLA\n", 56 | "\n", 57 | "import qiskit_alt, qiskit_alt.electronic_structure # already done above, but this does not hurt\n", 58 | "from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp\n", 59 | "from qiskit.opflow import PauliSumOp" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "a43d4152", 65 | "metadata": {}, 66 | "source": [ 67 | "

Describing the $H_2$ molecule structure

" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 4, 73 | "id": "4891a609", 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "geometry = [[\"H\", [0.0, 0.0, 0.0]], [\"H\", [0.0, 0.0, 0.735]]]\n", 78 | "basis=\"sto3g\"\n", 79 | "\n", 80 | "molecule = Molecule(\n", 81 | " geometry=geometry, charge=0, multiplicity=1\n", 82 | ")\n", 83 | "driver = ElectronicStructureMoleculeDriver(\n", 84 | " molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF\n", 85 | ")" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "15dd86ae", 91 | "metadata": {}, 92 | "source": [ 93 | "

Computing the fermionic operator

" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "49bc104c", 99 | "metadata": {}, 100 | "source": [ 101 | "

Qiskit Nature

" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 5, 107 | "id": "405a85be", 108 | "metadata": { 109 | "scrolled": false 110 | }, 111 | "outputs": [ 112 | { 113 | "data": { 114 | "text/plain": [ 115 | "SparsePauliOp(['YYYY', 'XXYY', 'YYXX', 'XXXX', 'IIII', 'ZIII', 'IZII', 'ZZII', 'IIZI', 'ZIZI', 'IZZI', 'IIIZ', 'ZIIZ', 'IZIZ', 'IIZZ'],\n", 116 | " coeffs=[ 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j,\n", 117 | " -0.81054798+0.j, -0.22575349+0.j, 0.17218393+0.j, 0.12091263+0.j,\n", 118 | " -0.22575349+0.j, 0.17464343+0.j, 0.16614543+0.j, 0.17218393+0.j,\n", 119 | " 0.16614543+0.j, 0.16892754+0.j, 0.12091263+0.j])" 120 | ] 121 | }, 122 | "execution_count": 5, 123 | "metadata": {}, 124 | "output_type": "execute_result" 125 | } 126 | ], 127 | "source": [ 128 | "es_problem = ElectronicStructureProblem(driver)\n", 129 | "second_q_op= es_problem.second_q_ops()\n", 130 | "qubit_converter = QubitConverter(mapper=JordanWignerMapper())\n", 131 | "qubit_op = qubit_converter.convert(second_q_op[0])\n", 132 | "# Print the underlying data stored backing qubit_op\n", 133 | "qubit_op.primitive.simplify()" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "01277c4a", 139 | "metadata": {}, 140 | "source": [ 141 | "

Qiskit Alt

" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 6, 147 | "id": "ed086717", 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "# Compute the Fermionic operator of the molecule \n", 152 | "fermi_op = qiskit_alt.electronic_structure.fermionic_hamiltonian(geometry, basis)\n", 153 | "\n", 154 | "# Convert the Fermionic operator to a Pauli operator using the Jordan-Wigner transform\n", 155 | "pauli_op = qiskit_alt.electronic_structure.jordan_wigner(fermi_op)" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "b88169fe", 161 | "metadata": {}, 162 | "source": [ 163 | "`jordan_wigner` did the computation in Julia and converted the result to standard Python qiskit `SparsePauliOp` before returning:" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 7, 169 | "id": "c051483e", 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "data": { 174 | "text/plain": [ 175 | "qiskit.quantum_info.operators.symplectic.sparse_pauli_op.SparsePauliOp" 176 | ] 177 | }, 178 | "execution_count": 7, 179 | "metadata": {}, 180 | "output_type": "execute_result" 181 | } 182 | ], 183 | "source": [ 184 | "type(pauli_op)" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "id": "7beb6d8f", 190 | "metadata": {}, 191 | "source": [ 192 | "It displays like this." 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 8, 198 | "id": "9b7328cf", 199 | "metadata": { 200 | "scrolled": true 201 | }, 202 | "outputs": [ 203 | { 204 | "data": { 205 | "text/plain": [ 206 | "SparsePauliOp(['IIII', 'ZIII', 'IZII', 'ZZII', 'IIZI', 'ZIZI', 'IZZI', 'XXXX', 'YYXX', 'XXYY', 'YYYY', 'IIIZ', 'ZIIZ', 'IZIZ', 'IIZZ'],\n", 207 | " coeffs=[-0.09057899+0.j, -0.22575349+0.j, 0.17218393+0.j, 0.12091263+0.j,\n", 208 | " -0.22575349+0.j, 0.17464343+0.j, 0.16614543+0.j, 0.0452328 +0.j,\n", 209 | " 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j, 0.17218393+0.j,\n", 210 | " 0.16614543+0.j, 0.16892754+0.j, 0.12091263+0.j])" 211 | ] 212 | }, 213 | "execution_count": 8, 214 | "metadata": {}, 215 | "output_type": "execute_result" 216 | } 217 | ], 218 | "source": [ 219 | "pauli_op.simplify()" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "id": "318347f6", 225 | "metadata": {}, 226 | "source": [ 227 | "The two calculations of the Hamiltonian as a Pauli operator agree, up to the constant term." 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": 9, 233 | "id": "31c2519e", 234 | "metadata": { 235 | "scrolled": true 236 | }, 237 | "outputs": [ 238 | { 239 | "data": { 240 | "text/plain": [ 241 | "SparsePauliOp(['IIII'],\n", 242 | " coeffs=[0.71996899+0.j])" 243 | ] 244 | }, 245 | "execution_count": 9, 246 | "metadata": {}, 247 | "output_type": "execute_result" 248 | } 249 | ], 250 | "source": [ 251 | "(pauli_op - qubit_op.primitive).simplify()" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "id": "da126f1d", 257 | "metadata": {}, 258 | "source": [ 259 | "This is just the nuclear-repulsion term, which is retained by qiskit_alt. Note the coefficient is the same as the line above." 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": 10, 265 | "id": "51903aca", 266 | "metadata": { 267 | "scrolled": true 268 | }, 269 | "outputs": [ 270 | { 271 | "data": { 272 | "text/input": [ 273 | "QuantumOps.FermiTerm(\"IIII\", 0.7199689944489797)" 274 | ], 275 | "text/plain": [ 276 | "4-factor QuantumOps.FermiTerm{Vector{QuantumOps.FermiOps.FermiOp}, Float64}:\n", 277 | "IIII * 0.7199689944489797" 278 | ] 279 | }, 280 | "execution_count": 10, 281 | "metadata": {}, 282 | "output_type": "execute_result" 283 | } 284 | ], 285 | "source": [ 286 | "fermi_op[1] # In qiskit_alt, the Fermi operator carries the identity term" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": 11, 292 | "id": "a81c20f6", 293 | "metadata": {}, 294 | "outputs": [ 295 | { 296 | "data": { 297 | "text/plain": [ 298 | "0.7199689944489797" 299 | ] 300 | }, 301 | "execution_count": 11, 302 | "metadata": {}, 303 | "output_type": "execute_result" 304 | } 305 | ], 306 | "source": [ 307 | "nuclear_energy_term = fermi_op[1].coeff\n", 308 | "nuclear_energy_term" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "id": "7fca0494", 314 | "metadata": {}, 315 | "source": [ 316 | "

Computing the ground state energy

" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 12, 322 | "id": "6fcb30bf", 323 | "metadata": {}, 324 | "outputs": [], 325 | "source": [ 326 | "# Define the ansatz\n", 327 | "ansatz = TwoLocal(\n", 328 | " rotation_blocks=[\"h\", \"rx\"],\n", 329 | " entanglement_blocks=\"cz\",\n", 330 | " entanglement=\"full\",\n", 331 | " reps=2,\n", 332 | " parameter_prefix=\"y\",\n", 333 | ")\n", 334 | "\n", 335 | "# Initialize the COBYLA optimizer\n", 336 | "optimizer = COBYLA(maxiter=500)\n", 337 | "\n", 338 | "# Select the backend for the quantum_instance\n", 339 | "backend = QuantumInstance(Aer.get_backend('statevector_simulator'), \n", 340 | " seed_simulator=100, \n", 341 | " seed_transpiler=100)\n", 342 | "# Run VQE algorithm\n", 343 | "vqe = VQE(ansatz = ansatz, \n", 344 | " optimizer = optimizer,\n", 345 | " quantum_instance = backend)" 346 | ] 347 | }, 348 | { 349 | "cell_type": "markdown", 350 | "id": "891e8735", 351 | "metadata": {}, 352 | "source": [ 353 | "

Qiskit Nature

" 354 | ] 355 | }, 356 | { 357 | "cell_type": "code", 358 | "execution_count": 13, 359 | "id": "809bfe8d", 360 | "metadata": {}, 361 | "outputs": [ 362 | { 363 | "name": "stdout", 364 | "output_type": "stream", 365 | "text": [ 366 | "The ground-state energy of the Hydrogen molecule is -1.117 Hatree\n" 367 | ] 368 | } 369 | ], 370 | "source": [ 371 | "# Compute the ground-state energy of the molecule \n", 372 | "nature_result = vqe.compute_minimum_eigenvalue(operator=qubit_op)\n", 373 | "nature_energy = nature_result.eigenvalue.real + nuclear_energy_term # Include constant term that is normally thrown away\n", 374 | "print(\"The ground-state energy of the Hydrogen molecule is {} Hatree\".format(round(nature_energy,3)))" 375 | ] 376 | }, 377 | { 378 | "cell_type": "markdown", 379 | "id": "67d6415e", 380 | "metadata": {}, 381 | "source": [ 382 | "

Qiskit Alt

" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": 14, 388 | "id": "485b4bce", 389 | "metadata": {}, 390 | "outputs": [], 391 | "source": [ 392 | "# Convert the Pauli operator into a sum of Pauli operators, the required input format\n", 393 | "pauli_sum_op = PauliSumOp(pauli_op)" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": 15, 399 | "id": "c3775805", 400 | "metadata": {}, 401 | "outputs": [ 402 | { 403 | "name": "stdout", 404 | "output_type": "stream", 405 | "text": [ 406 | "The ground-state energy of the Hydrogen molecule is -1.117 Hatree\n" 407 | ] 408 | } 409 | ], 410 | "source": [ 411 | "# Compute the ground-state energy of the molecule\n", 412 | "alt_result = vqe.compute_minimum_eigenvalue(operator=pauli_sum_op)\n", 413 | "print(\"The ground-state energy of the Hydrogen molecule is {} Hatree\".format(round(alt_result.eigenvalue.real,3)))" 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": null, 419 | "id": "3a4f08a3", 420 | "metadata": {}, 421 | "outputs": [], 422 | "source": [] 423 | } 424 | ], 425 | "metadata": { 426 | "kernelspec": { 427 | "display_name": "Python 3", 428 | "language": "python", 429 | "name": "python3" 430 | }, 431 | "language_info": { 432 | "codemirror_mode": { 433 | "name": "ipython", 434 | "version": 3 435 | }, 436 | "file_extension": ".py", 437 | "mimetype": "text/x-python", 438 | "name": "python", 439 | "nbconvert_exporter": "python", 440 | "pygments_lexer": "ipython3", 441 | "version": "3.9.10" 442 | } 443 | }, 444 | "nbformat": 4, 445 | "nbformat_minor": 5 446 | } 447 | -------------------------------------------------------------------------------- /demos/qiskit_alt_demo_jc.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "3e23cbd3", 6 | "metadata": {}, 7 | "source": [ 8 | "# qiskit_alt\n", 9 | "\n", 10 | "This python package provides a thin wrapper around some features of Qiskit that have been (re-)implemented in Julia and provides a Python interface. The input and output of the Python interface are the same as the input and output to Python qiskit. At present, we have prepared two high level demonstrations\n", 11 | "\n", 12 | "* Performing the Jordan-Wigner transform from a Fermionic operator to a Pauli operator.\n", 13 | "\n", 14 | "* Computing the Fermionic operator from integrals computed by `pyscf`.\n", 15 | "\n", 16 | "In both cases, we will see that the `qiskit_alt` implementation is much more performant.\n", 17 | "\n", 18 | "We have also prepared some lower-level demonstrations of performance gains\n", 19 | "\n", 20 | "* Converting an operator from the computational basis to the Pauli basis.\n", 21 | "\n", 22 | "* Creating a `SparsePauliOp` from a list of strings" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "86f7fd8f", 28 | "metadata": {}, 29 | "source": [ 30 | "The Python package has been installed in a virtual environment created with `python -m venv ./env`. The Julia packages have been installed in a local environment in the standard way, via a spec in `Project.toml` file.\n", 31 | "\n", 32 | "When we import the package `qiskit_alt`, the Julia environment is also activated.\n", 33 | "There are two options for communcating with Julia: `PyCall.jl/pyjulia` and `PythonCall.jl/juliacall`.\n", 34 | "Here we use the second by passing `calljulia=\"juliacall\"` when initializing." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 1, 40 | "id": "29b7099b", 41 | "metadata": { 42 | "scrolled": true 43 | }, 44 | "outputs": [], 45 | "source": [ 46 | "import qiskit_alt\n", 47 | "qiskit_alt.project.ensure_init(calljulia=\"juliacall\", compile=False)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "id": "cb29172e", 53 | "metadata": {}, 54 | "source": [ 55 | "We assume that no one is familiar with Julia, much less with `juliacall`, the package we use to call Julia from Python. So, we inject a bit of tutorial.\n", 56 | "\n", 57 | "The default `Module` in Julia `Main` is available. You can think of it as a namespace. And, as always, objects from the `Module` `Base` have been imported into `Main`.\n", 58 | "\n", 59 | "As an example of how `juliacall` works, we create an `Array` of `Float64` zeros on the Julia side. On the Python side, they are dispalyed as they would in Julia." 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "id": "0f936bc8", 66 | "metadata": { 67 | "scrolled": true 68 | }, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/csv": [ 73 | "0.0\n", 74 | "0.0\n", 75 | "0.0\n" 76 | ], 77 | "text/plain": [ 78 | "3-element Vector{Float64}:\n", 79 | " 0.0\n", 80 | " 0.0\n", 81 | " 0.0" 82 | ], 83 | "text/tab-separated-values": [ 84 | "0.0\n", 85 | "0.0\n", 86 | "0.0\n" 87 | ] 88 | }, 89 | "execution_count": 2, 90 | "metadata": {}, 91 | "output_type": "execute_result" 92 | } 93 | ], 94 | "source": [ 95 | "julia = qiskit_alt.project.julia\n", 96 | "Main = julia.Main\n", 97 | "julia.Main.zeros(3)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "id": "fb44499b", 103 | "metadata": {}, 104 | "source": [ 105 | "However, we can see that the Julia `Vector` is in fact wrapped in a Python class." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 3, 111 | "id": "ab337392", 112 | "metadata": {}, 113 | "outputs": [ 114 | { 115 | "data": { 116 | "text/plain": [ 117 | "juliacall.VectorValue" 118 | ] 119 | }, 120 | "execution_count": 3, 121 | "metadata": {}, 122 | "output_type": "execute_result" 123 | } 124 | ], 125 | "source": [ 126 | "type(julia.Main.zeros(3))" 127 | ] 128 | }, 129 | { 130 | "cell_type": "markdown", 131 | "id": "b733f29d", 132 | "metadata": {}, 133 | "source": [ 134 | "There are several ways to call Julia from Python and vice versa, and to specifiy features such as the copying vs. sharing semantics. We won't go into much of this in this demo." 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "dea87cf9", 140 | "metadata": {}, 141 | "source": [ 142 | "## Electronic structure\n", 143 | "\n", 144 | "Part of a workflow for, say, VQE involves using qiskit-nature to do the following:\n", 145 | "* Get a description of a model Hamiltonian from the package `pyscf` by passing it a description of the geometry of a molecule.\n", 146 | "* Convert that description of a Hamiltonian to a qiskit-native Fermionic operator.\n", 147 | "* Convert the Fermionic operator to a qubit operator expressed in the Pauli basis.\n", 148 | "\n", 149 | "The last step above may be done in several ways, one of which is known as the Jordan-Wigner transform. It is this step that we will benchmark here." 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "c4ca7000", 155 | "metadata": {}, 156 | "source": [ 157 | "### qiskit-nature\n", 158 | "\n", 159 | "First, we see how this is done in qiskit-nature. We need to specify the geometry of the molecule and the\n", 160 | "[basis set](https://en.wikipedia.org/wiki/Basis_set_(chemistry)). We choose `sto3g`, one of the smallest, simplest, basis sets." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 4, 166 | "id": "ca1047f9", 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "from qiskit_nature.drivers import UnitsType, Molecule\n", 171 | "from qiskit_nature.drivers.second_quantization import ElectronicStructureDriverType, ElectronicStructureMoleculeDriver\n", 172 | "\n", 173 | "# Specify the geometry of the H_2 molecule\n", 174 | "geometry = [['H', [0., 0., 0.]],\n", 175 | " ['H', [0., 0., 0.735]]]\n", 176 | "\n", 177 | "basis = 'sto3g'" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "id": "c6726e7d", 183 | "metadata": {}, 184 | "source": [ 185 | "Then, we compute the fermionic Hamiltonian like this." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 5, 191 | "id": "fc1697b6", 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "molecule = Molecule(geometry=geometry,\n", 196 | " charge=0, multiplicity=1)\n", 197 | "driver = ElectronicStructureMoleculeDriver(molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF)\n", 198 | "\n", 199 | "from qiskit_nature.problems.second_quantization import ElectronicStructureProblem\n", 200 | "from qiskit_nature.converters.second_quantization import QubitConverter\n", 201 | "from qiskit_nature.mappers.second_quantization import JordanWignerMapper\n", 202 | "\n", 203 | "es_problem = ElectronicStructureProblem(driver)\n", 204 | "second_q_op = es_problem.second_q_ops()\n", 205 | "\n", 206 | "fermionic_hamiltonian = second_q_op[0]" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "id": "f6000f0a", 212 | "metadata": {}, 213 | "source": [ 214 | "The Jordan-Wigner transform is performed like this." 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": 6, 220 | "id": "1d41dd67", 221 | "metadata": {}, 222 | "outputs": [ 223 | { 224 | "data": { 225 | "text/plain": [ 226 | "SparsePauliOp(['YYYY', 'XXYY', 'YYXX', 'XXXX', 'IIII', 'ZIII', 'IZII', 'ZZII', 'IIZI', 'ZIZI', 'IZZI', 'IIIZ', 'ZIIZ', 'IZIZ', 'IIZZ'],\n", 227 | " coeffs=[ 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j,\n", 228 | " -0.81054798+0.j, -0.22575349+0.j, 0.17218393+0.j, 0.12091263+0.j,\n", 229 | " -0.22575349+0.j, 0.17464343+0.j, 0.16614543+0.j, 0.17218393+0.j,\n", 230 | " 0.16614543+0.j, 0.16892754+0.j, 0.12091263+0.j])" 231 | ] 232 | }, 233 | "execution_count": 6, 234 | "metadata": {}, 235 | "output_type": "execute_result" 236 | } 237 | ], 238 | "source": [ 239 | "qubit_converter = QubitConverter(mapper=JordanWignerMapper())\n", 240 | "nature_qubit_op = qubit_converter.convert(fermionic_hamiltonian)\n", 241 | "nature_qubit_op.primitive" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "id": "f00c5538", 247 | "metadata": {}, 248 | "source": [ 249 | "### qiskit_alt\n", 250 | "\n", 251 | "The only high-level code in `qiskit_alt` was written to support this demo. So doing the JW-transform is less verbose." 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 7, 257 | "id": "7ecebbe7", 258 | "metadata": {}, 259 | "outputs": [ 260 | { 261 | "data": { 262 | "text/plain": [ 263 | "SparsePauliOp(['IIII', 'ZIII', 'IZII', 'ZZII', 'IIZI', 'ZIZI', 'IZZI', 'XXXX', 'YYXX', 'XXYY', 'YYYY', 'IIIZ', 'ZIIZ', 'IZIZ', 'IIZZ'],\n", 264 | " coeffs=[-0.09057899+0.j, -0.22575349+0.j, 0.17218393+0.j, 0.12091263+0.j,\n", 265 | " -0.22575349+0.j, 0.17464343+0.j, 0.16614543+0.j, 0.0452328 +0.j,\n", 266 | " 0.0452328 +0.j, 0.0452328 +0.j, 0.0452328 +0.j, 0.17218393+0.j,\n", 267 | " 0.16614543+0.j, 0.16892754+0.j, 0.12091263+0.j])" 268 | ] 269 | }, 270 | "execution_count": 7, 271 | "metadata": {}, 272 | "output_type": "execute_result" 273 | } 274 | ], 275 | "source": [ 276 | "import qiskit_alt.electronic_structure\n", 277 | "fermi_op = qiskit_alt.electronic_structure.fermionic_hamiltonian(geometry, basis)\n", 278 | "pauli_op = qiskit_alt.electronic_structure.jordan_wigner(fermi_op)\n", 279 | "pauli_op.simplify() # The Julia Pauli operators use a different sorting convention; we sort again for comparison. " 280 | ] 281 | }, 282 | { 283 | "cell_type": "markdown", 284 | "id": "3d6b0ffa", 285 | "metadata": {}, 286 | "source": [ 287 | "Note that the constant term differs. The qiskit-nature version ignores the nuclear-repulsion term. I need to open an issue about whether and how to handle it." 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "id": "f32494c5", 293 | "metadata": {}, 294 | "source": [ 295 | "### Benchmarking\n", 296 | "\n", 297 | "Computing the Hamiltonian for a larger molecule or a larger basis set takes more time and produces a Hamiltonian with more factors and terms. Here we compare the performance of `qiskit_alt` and `qiskit-nature` on combinations of $\\text{H}_2$ and $\\text{H}_2\\text{O}$ molecules for several basis sets." 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "id": "9c118b30", 303 | "metadata": {}, 304 | "source": [ 305 | "First we benchmark qiskit-nature, and record the times in `nature_times`." 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 8, 311 | "id": "83562a9c", 312 | "metadata": {}, 313 | "outputs": [ 314 | { 315 | "name": "stdout", 316 | "output_type": "stream", 317 | "text": [ 318 | "geometry=h2_geometry, basis='sto3g' 3.30 ms\n", 319 | "geometry=h2_geometry, basis='631g' 39.01 ms\n", 320 | "geometry=h2_geometry, basis='631++g' 201.21 ms\n", 321 | "geometry=h2o_geometry, basis='sto3g' 235.18 ms\n", 322 | "geometry=h2o_geometry, basis='631g' 3103.37 ms\n" 323 | ] 324 | }, 325 | { 326 | "data": { 327 | "text/plain": [ 328 | "[3.300445107743144,\n", 329 | " 39.00811760686338,\n", 330 | " 201.20953600853682,\n", 331 | " 235.17840416170657,\n", 332 | " 3103.3738718833774]" 333 | ] 334 | }, 335 | "execution_count": 8, 336 | "metadata": {}, 337 | "output_type": "execute_result" 338 | } 339 | ], 340 | "source": [ 341 | "%run ../bench/jordan_wigner_nature_time.py\n", 342 | "nature_times" 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "id": "c6f6e210", 348 | "metadata": {}, 349 | "source": [ 350 | "Next we benchmark qiskit_alt, and record the times in `alt_times`." 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": 9, 356 | "id": "56f3abc4", 357 | "metadata": { 358 | "scrolled": true 359 | }, 360 | "outputs": [ 361 | { 362 | "name": "stdout", 363 | "output_type": "stream", 364 | "text": [ 365 | "geometry=h2_geometry, basis='sto3g' 0.18 ms\n", 366 | "geometry=h2_geometry, basis='631g' 2.35 ms\n", 367 | "geometry=h2_geometry, basis='631++g' 19.60 ms\n", 368 | "geometry=h2o_geometry, basis='sto3g' 15.87 ms\n", 369 | "geometry=h2o_geometry, basis='631g' 556.02 ms\n" 370 | ] 371 | }, 372 | { 373 | "data": { 374 | "text/plain": [ 375 | "[0.17719920724630356,\n", 376 | " 2.354525704868138,\n", 377 | " 19.604289019480348,\n", 378 | " 15.869565401226282,\n", 379 | " 556.0218370053917]" 380 | ] 381 | }, 382 | "execution_count": 9, 383 | "metadata": {}, 384 | "output_type": "execute_result" 385 | } 386 | ], 387 | "source": [ 388 | "%run ../bench/jordan_wigner_alt_time.py\n", 389 | "alt_times" 390 | ] 391 | }, 392 | { 393 | "cell_type": "markdown", 394 | "id": "74a38fe5", 395 | "metadata": {}, 396 | "source": [ 397 | "We compare the relative performance." 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": 10, 403 | "id": "8269b475", 404 | "metadata": { 405 | "scrolled": false 406 | }, 407 | "outputs": [ 408 | { 409 | "data": { 410 | "text/plain": [ 411 | "[18.62562005232669,\n", 412 | " 16.567293160661407,\n", 413 | " 10.263546706978222,\n", 414 | " 14.81946091249189,\n", 415 | " 5.58138847315323]" 416 | ] 417 | }, 418 | "execution_count": 10, 419 | "metadata": {}, 420 | "output_type": "execute_result" 421 | } 422 | ], 423 | "source": [ 424 | "[t_nature / t_qk_alt for t_nature, t_qk_alt in zip(nature_times, alt_times)]" 425 | ] 426 | }, 427 | { 428 | "cell_type": "markdown", 429 | "id": "e7e0524b", 430 | "metadata": {}, 431 | "source": [ 432 | "We see that\n", 433 | "* qiskit_alt is at least ten times faster\n", 434 | "* The relative performance increases as the problem in some sense gets larger.\n", 435 | "\n", 436 | "In fact, another problem, not shown here, finishes in 18s with qiskit_alt and in 5730s in qiskit-nature.\n", 437 | "In this case, `qiskit_alt` is 320 times faster than `qiskit-nature`. I don't have an idea about the origin of this scaling." 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "id": "f6ca3e40", 443 | "metadata": {}, 444 | "source": [ 445 | "### Computing the Fermonic operator\n", 446 | "\n", 447 | "Computing the Fermionic operator from the output of `pyscf` is also much more efficient in `qiskit_alt`." 448 | ] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "id": "f2dd8cfa", 453 | "metadata": {}, 454 | "source": [ 455 | "We benchmark qiskit-nature computing the fermionic Hamiltonian" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 11, 461 | "id": "1eb2798b", 462 | "metadata": {}, 463 | "outputs": [ 464 | { 465 | "name": "stdout", 466 | "output_type": "stream", 467 | "text": [ 468 | "geometry=h2_geometry, basis='sto3g' 59.96 ms\n", 469 | "geometry=h2_geometry, basis='631g' 101.52 ms\n", 470 | "geometry=h2_geometry, basis='631++g' 310.82 ms\n", 471 | "geometry=h2o_geometry, basis='sto3g' 446.44 ms\n", 472 | "geometry=h2o_geometry, basis='631g' 24391.53 ms\n" 473 | ] 474 | }, 475 | { 476 | "data": { 477 | "text/plain": [ 478 | "[59.957073791883886,\n", 479 | " 101.52261711191386,\n", 480 | " 310.81768460571766,\n", 481 | " 446.4369765948504,\n", 482 | " 24391.52706321329]" 483 | ] 484 | }, 485 | "execution_count": 11, 486 | "metadata": {}, 487 | "output_type": "execute_result" 488 | } 489 | ], 490 | "source": [ 491 | "%run ../bench/fermionic_nature_time.py\n", 492 | "nature_times" 493 | ] 494 | }, 495 | { 496 | "cell_type": "markdown", 497 | "id": "a56c2fd1", 498 | "metadata": {}, 499 | "source": [ 500 | "We benchmark qiskit_alt computing the fermionic Hamiltonian." 501 | ] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": 12, 506 | "id": "b6650d69", 507 | "metadata": {}, 508 | "outputs": [ 509 | { 510 | "name": "stdout", 511 | "output_type": "stream", 512 | "text": [ 513 | "geometry=h2_geometry, basis='sto3g' 58.65 ms\n", 514 | "geometry=h2_geometry, basis='631g' 62.26 ms\n", 515 | "geometry=h2_geometry, basis='631++g' 64.02 ms\n", 516 | "geometry=h2o_geometry, basis='sto3g' 75.56 ms\n", 517 | "geometry=h2o_geometry, basis='631g' 181.35 ms\n" 518 | ] 519 | }, 520 | { 521 | "data": { 522 | "text/plain": [ 523 | "[58.645384199917316,\n", 524 | " 62.25809780880809,\n", 525 | " 64.02283560018986,\n", 526 | " 75.55517368018627,\n", 527 | " 181.35322742164135]" 528 | ] 529 | }, 530 | "execution_count": 12, 531 | "metadata": {}, 532 | "output_type": "execute_result" 533 | } 534 | ], 535 | "source": [ 536 | "%run ../bench/fermionic_alt_time.py\n", 537 | "alt_times" 538 | ] 539 | }, 540 | { 541 | "cell_type": "markdown", 542 | "id": "0c8e4ac9", 543 | "metadata": {}, 544 | "source": [ 545 | "We compare the relative performance." 546 | ] 547 | }, 548 | { 549 | "cell_type": "code", 550 | "execution_count": 13, 551 | "id": "e197f5fb", 552 | "metadata": {}, 553 | "outputs": [ 554 | { 555 | "data": { 556 | "text/plain": [ 557 | "[1.0223664591827915,\n", 558 | " 1.6306732888577065,\n", 559 | " 4.85479410107221,\n", 560 | " 5.908754554447207,\n", 561 | " 134.49734206551312]" 562 | ] 563 | }, 564 | "execution_count": 13, 565 | "metadata": {}, 566 | "output_type": "execute_result" 567 | } 568 | ], 569 | "source": [ 570 | "[t_nature / t_qk_alt for t_nature, t_qk_alt in zip(nature_times, alt_times)]" 571 | ] 572 | }, 573 | { 574 | "cell_type": "markdown", 575 | "id": "ef5d18bf", 576 | "metadata": {}, 577 | "source": [ 578 | "We see again that, as the problem size increases, `qiskit_alt` is increasingly more performant." 579 | ] 580 | }, 581 | { 582 | "cell_type": "markdown", 583 | "id": "bdfd094a", 584 | "metadata": {}, 585 | "source": [ 586 | "## Discussion\n", 587 | "\n", 588 | "The Julia implemenation consists of these packages\n", 589 | "\n", 590 | "* [`QuantumOps.jl`](https://github.com/Qiskit-Extensions/QuantumOps.jl) implementing Fermionic and Pauli operators and calculations using them.\n", 591 | "\n", 592 | "* [`ElectronicStructure.jl`](https://github.com/Qiskit-Extensions/ElectronicStructure.jl) provides an interface to electronic structure packages.\n", 593 | "\n", 594 | "* [`ElectronicStructurePySCF.jl`](https://github.com/Qiskit-Extensions/ElectronicStructurePySCF.jl) provides an interface to `pyscf`\n", 595 | "\n", 596 | "* [`QiskitQuantumInfo.jl`](https://github.com/Qiskit-Extensions/QiskitQuantumInfo.jl) provides data structures that mirror Python Qiskit data structures. These are used as intermedidate structures for converting from `QuantumOps` and `ElectronicStructure` to Python Qiskit. In the future these might be used directly for calculations.\n", 597 | "\n", 598 | "\n", 599 | "The Python interface is a Python package `qiskit_alt`. This could contain a mixture of Julia and Python code. Or all the Julia code might be moved to the Julia packages.\n", 600 | "\n", 601 | "### Implementation\n", 602 | "\n", 603 | "In the examples above, the following happens.\n", 604 | "\n", 605 | "* Julia code calls `pyscf` and stores the results in Julia data structures.\n", 606 | "\n", 607 | "* These data are used to construct a Fermionic operator as a data structure defined in `QuantumOps`.\n", 608 | "\n", 609 | "* The Jordan-Wigner transform, implemented in `QuantumOps` is used to compute a Pauli operator.\n", 610 | "\n", 611 | "* The Pauli operator (as a structure in `QuantumOps`) is converted to a Qiskit-like operator defined in `QiskitQuantumInfo.jl`.\n", 612 | "\n", 613 | "* The operator defined in `QiskitQuantumInfo.jl` is sent to Python and converted to numpy arrays, which are then used to construct native Qiskit types. The conversion to numpy arrays is provided by `pyjulia`.\n", 614 | "\n", 615 | "### Complexity, dynamism\n", 616 | "\n", 617 | "* It is worth noting that operators in `QuantumOps` are *not* highly optimized implementations. In fact, much of the code for the two types of operators is shared, they inherit from a parent class. There are other implementations of Pauli operators in Julia that are much more efficient for instance in [`QuantumClifford.jl`](https://github.com/Krastanov/QuantumClifford.jl).\n", 618 | "\n", 619 | "* [Issue](https://github.com/Qiskit-Extensions/QuantumOps.jl/issues/17) for improving performance of Jordan-Wigner in `QuantumOps`.\n", 620 | " * Precompute one and two-body terms\n", 621 | " * Use @threads\n" 622 | ] 623 | }, 624 | { 625 | "cell_type": "markdown", 626 | "id": "af37da81", 627 | "metadata": {}, 628 | "source": [ 629 | "# More demos\n", 630 | "\n", 631 | "Here are some smaller scale demonstrations.\n", 632 | "\n", 633 | "## Converting a matrix to the Pauli basis\n", 634 | "\n", 635 | "Here we convert a matrix representing an operator in the computational basis to the Pauli basis.\n", 636 | "In this case, `qiskit_alt` is much more performant than `qiskit.quantum_info`.\n", 637 | "This is how it is done in `QuantumOps`." 638 | ] 639 | }, 640 | { 641 | "cell_type": "code", 642 | "execution_count": 14, 643 | "id": "9bda9915", 644 | "metadata": {}, 645 | "outputs": [ 646 | { 647 | "data": { 648 | "text/plain": [ 649 | "SparsePauliOp(['III', 'XII', 'YII', 'ZII', 'IXI', 'XXI', 'YXI', 'ZXI', 'IYI', 'XYI', 'YYI', 'ZYI', 'IZI', 'XZI', 'YZI', 'ZZI', 'IIX', 'XIX', 'YIX', 'ZIX', 'IXX', 'XXX', 'YXX', 'ZXX', 'IYX', 'XYX', 'YYX', 'ZYX', 'IZX', 'XZX', 'YZX', 'ZZX', 'IIY', 'XIY', 'YIY', 'ZIY', 'IXY', 'XXY', 'YXY', 'ZXY', 'IYY', 'XYY', 'YYY', 'ZYY', 'IZY', 'XZY', 'YZY', 'ZZY', 'IIZ', 'XIZ', 'YIZ', 'ZIZ', 'IXZ', 'XXZ', 'YXZ', 'ZXZ', 'IYZ', 'XYZ', 'YYZ', 'ZYZ', 'IZZ', 'XZZ', 'YZZ', 'ZZZ'],\n", 650 | " coeffs=[ 0.58230825+0.j , 0.58188025+0.j , 0. +0.10409742j,\n", 651 | " 0.03180181+0.j , 0.6945495 +0.j , 0.42739346+0.j ,\n", 652 | " 0. -0.1045725j , 0.11544287+0.j , 0. +0.02631752j,\n", 653 | " 0. -0.02150802j, 0.00452969+0.j , 0. +0.00030698j,\n", 654 | " -0.0313073 +0.j , 0.06866987+0.j , 0. +0.13950625j,\n", 655 | " -0.04557353+0.j , 0.47431829+0.j , 0.45800551+0.j ,\n", 656 | " 0. +0.0270196j , -0.01194553+0.j , 0.33553012+0.j ,\n", 657 | " 0.51936366+0.j , 0. +0.02884795j, -0.09220285+0.j ,\n", 658 | " 0. +0.14056313j, 0. +0.02248184j, -0.03459762-0.j ,\n", 659 | " 0. -0.03023093j, -0.0032599 +0.j , 0.00529333+0.j ,\n", 660 | " 0. +0.0778217j , -0.23530792+0.j , 0. -0.11731648j,\n", 661 | " 0. -0.114612j , -0.04712552-0.j , 0. +0.06851582j,\n", 662 | " 0. -0.01789098j, 0. +0.07428848j, 0.22493813+0.j ,\n", 663 | " 0. +0.1713509j , 0.03379485+0.j , -0.10877464-0.j ,\n", 664 | " 0. -0.20187169j, -0.13690245-0.j , 0. +0.09061317j,\n", 665 | " 0. +0.03400482j, 0.08564516+0.j , 0. +0.15177087j,\n", 666 | " 0.18712635+0.j , -0.24255515+0.j , 0. +0.12395224j,\n", 667 | " 0.13855542+0.j , 0.11939501+0.j , 0.11732131+0.j ,\n", 668 | " 0. +0.0707233j , -0.12595996+0.j , 0. +0.03406474j,\n", 669 | " 0. +0.07313686j, 0.07391283+0.j , 0. +0.03980845j,\n", 670 | " 0.21565409+0.j , 0.06438585+0.j , 0. +0.10269266j,\n", 671 | " -0.13519153+0.j ])" 672 | ] 673 | }, 674 | "execution_count": 14, 675 | "metadata": {}, 676 | "output_type": "execute_result" 677 | } 678 | ], 679 | "source": [ 680 | "from qiskit_alt.pauli_operators import QuantumOps, PauliSum_to_SparsePauliOp\n", 681 | "import numpy as np\n", 682 | "m = np.random.rand(2**3, 2**3) # 3-qubit operator\n", 683 | "m = Main.convert(Main.Matrix, m) # Convert PythonCall.PyArray to a native Julia type\n", 684 | "pauli_sum = QuantumOps.PauliSum(m) # This is a wrapped Julia object\n", 685 | "PauliSum_to_SparsePauliOp(pauli_sum) # Convert to qiskit.quantum_info.SparsePauliOp" 686 | ] 687 | }, 688 | { 689 | "cell_type": "markdown", 690 | "id": "0bfdfacb", 691 | "metadata": {}, 692 | "source": [ 693 | "When using `pyjulia`, the `numpy` matrix is automatically converted to a Julia `Matrix`.\n", 694 | "But, here, we are using `juliacall`. In this case the `numpy` matrix is wrapped in `PythonCall.PyArray`, a subtype of `AbstractMatrix` that is particular to `juliacall`. It supports iteration in Julia, but the iterator calls python. The performance in this case is much worse. For this reason, we convert it to a matrix of Julia floats via\n", 695 | "`m = Main.convert(Main.Matrix, m)`" 696 | ] 697 | }, 698 | { 699 | "cell_type": "markdown", 700 | "id": "9268c181", 701 | "metadata": {}, 702 | "source": [ 703 | "We run benchmarks of conversion of matrices to the Pauli basis." 704 | ] 705 | }, 706 | { 707 | "cell_type": "code", 708 | "execution_count": 15, 709 | "id": "a6945b8f", 710 | "metadata": {}, 711 | "outputs": [ 712 | { 713 | "name": "stdout", 714 | "output_type": "stream", 715 | "text": [ 716 | "nqubits=2, 1.28 ms\n", 717 | "nqubits=3, 5.01 ms\n", 718 | "nqubits=4, 24.58 ms\n", 719 | "nqubits=5, 140.58 ms\n", 720 | "nqubits=6, 1020.32 ms\n", 721 | "nqubits=7, 8875.06 ms\n", 722 | "nqubits=8, 108370.44 ms\n" 723 | ] 724 | } 725 | ], 726 | "source": [ 727 | "%run ../bench/from_matrix_quantum_info.py" 728 | ] 729 | }, 730 | { 731 | "cell_type": "code", 732 | "execution_count": 16, 733 | "id": "465a7bf5", 734 | "metadata": {}, 735 | "outputs": [ 736 | { 737 | "name": "stdout", 738 | "output_type": "stream", 739 | "text": [ 740 | "nqubits=2, 0.23 ms\n", 741 | "nqubits=3, 0.31 ms\n", 742 | "nqubits=4, 10.84 ms\n", 743 | "nqubits=5, 27.14 ms\n", 744 | "nqubits=6, 25.03 ms\n", 745 | "nqubits=7, 103.43 ms\n", 746 | "nqubits=8, 553.45 ms\n" 747 | ] 748 | } 749 | ], 750 | "source": [ 751 | "%run ../bench/from_matrix_alt.py" 752 | ] 753 | }, 754 | { 755 | "cell_type": "markdown", 756 | "id": "94fd017c", 757 | "metadata": {}, 758 | "source": [ 759 | "Here are the ratios of the times for `qiskit.quantum_info` to those for `qiskit_alt`." 760 | ] 761 | }, 762 | { 763 | "cell_type": "code", 764 | "execution_count": 17, 765 | "id": "217f2f7a", 766 | "metadata": {}, 767 | "outputs": [ 768 | { 769 | "data": { 770 | "text/plain": [ 771 | "[5.6788111780212684,\n", 772 | " 16.4318143655766,\n", 773 | " 2.2672626078863733,\n", 774 | " 5.180106151548867,\n", 775 | " 40.759467962961715,\n", 776 | " 85.80711464351577,\n", 777 | " 195.80956101096078]" 778 | ] 779 | }, 780 | "execution_count": 17, 781 | "metadata": {}, 782 | "output_type": "execute_result" 783 | } 784 | ], 785 | "source": [ 786 | "[t_pyqk / t_qk_alt for t_pyqk, t_qk_alt in zip(pyqk_times, qk_alt_times)]" 787 | ] 788 | }, 789 | { 790 | "cell_type": "markdown", 791 | "id": "40fb432c", 792 | "metadata": {}, 793 | "source": [ 794 | "Again, the performance gain increases with the problem size." 795 | ] 796 | }, 797 | { 798 | "cell_type": "markdown", 799 | "id": "0cd10e98", 800 | "metadata": {}, 801 | "source": [ 802 | "## Creating a `SparsePauliOp` from a list of strings\n", 803 | "\n", 804 | "\n", 805 | "Here, we create a `SparsePauliOp` from a list of `n` strings, each with `k` single-Pauli factors, and simplify the result." 806 | ] 807 | }, 808 | { 809 | "cell_type": "markdown", 810 | "id": "fbfeab47", 811 | "metadata": {}, 812 | "source": [ 813 | "First, using `qiskit.quantum_info`" 814 | ] 815 | }, 816 | { 817 | "cell_type": "code", 818 | "execution_count": 18, 819 | "id": "39ad43f3", 820 | "metadata": {}, 821 | "outputs": [ 822 | { 823 | "name": "stdout", 824 | "output_type": "stream", 825 | "text": [ 826 | "k=10, n=10, 0.3342609968967736 ms\n", 827 | "k=10, n=100, 1.8926906515844166 ms\n", 828 | "k=10, n=1000, 16.779631900135428 ms\n", 829 | "k=10, n=5000, 83.44200999708846 ms\n", 830 | "k=10, n=10000, 168.6188920866698 ms\n", 831 | "k=10, n=100000, 1765.660970006138 ms\n", 832 | "k=100, n=10, 0.34020134480670094 ms\n", 833 | "k=100, n=100, 1.9124412443488836 ms\n", 834 | "k=100, n=1000, 17.62367991032079 ms\n", 835 | "k=100, n=5000, 89.54448039876297 ms\n", 836 | "k=100, n=10000, 180.06238690577447 ms\n", 837 | "k=100, n=100000, 1847.677995916456 ms\n" 838 | ] 839 | } 840 | ], 841 | "source": [ 842 | "%run ../bench/pauli_from_list_qinfo.py" 843 | ] 844 | }, 845 | { 846 | "cell_type": "markdown", 847 | "id": "bc7f9d07", 848 | "metadata": {}, 849 | "source": [ 850 | "Now, using `qiskit_alt`" 851 | ] 852 | }, 853 | { 854 | "cell_type": "code", 855 | "execution_count": 19, 856 | "id": "a4b9eeda", 857 | "metadata": {}, 858 | "outputs": [ 859 | { 860 | "name": "stdout", 861 | "output_type": "stream", 862 | "text": [ 863 | "k=10, n=10, 0.08436074713245034 ms\n", 864 | "k=10, n=100, 0.12164255604147911 ms\n", 865 | "k=10, n=1000, 0.5137079511769116 ms\n", 866 | "k=10, n=5000, 2.6032541529275477 ms\n", 867 | "k=10, n=10000, 6.878606008831412 ms\n", 868 | "k=10, n=100000, 94.18304250575602 ms\n", 869 | "k=100, n=10, 0.10068245464935899 ms\n", 870 | "k=100, n=100, 0.22444200003519654 ms\n", 871 | "k=100, n=1000, 1.5054140472784638 ms\n", 872 | "k=100, n=5000, 7.717218191828579 ms\n", 873 | "k=100, n=10000, 17.424617102369666 ms\n", 874 | "k=100, n=100000, 212.43814380140975 ms\n" 875 | ] 876 | } 877 | ], 878 | "source": [ 879 | "%run ../bench/pauli_from_list_alt.py" 880 | ] 881 | }, 882 | { 883 | "cell_type": "markdown", 884 | "id": "a1372173", 885 | "metadata": {}, 886 | "source": [ 887 | "The results were written to lists `quantum_info_times` and `qkalt_times`. We compare the performance:" 888 | ] 889 | }, 890 | { 891 | "cell_type": "code", 892 | "execution_count": 20, 893 | "id": "e7bfa245", 894 | "metadata": {}, 895 | "outputs": [ 896 | { 897 | "data": { 898 | "text/plain": [ 899 | "[3.9622811349924163,\n", 900 | " 15.559444927637205,\n", 901 | " 32.663757416432965,\n", 902 | " 32.05296336635126,\n", 903 | " 24.513526704419583,\n", 904 | " 18.74712180696678,\n", 905 | " 3.378953621974163,\n", 906 | " 8.520870621581427,\n", 907 | " 11.706865591018927,\n", 908 | " 11.603207033018409,\n", 909 | " 10.333793038200353,\n", 910 | " 8.697487008941728]" 911 | ] 912 | }, 913 | "execution_count": 20, 914 | "metadata": {}, 915 | "output_type": "execute_result" 916 | } 917 | ], 918 | "source": [ 919 | "[x / y for x,y in zip(quantum_info_times, qkalt_times)]" 920 | ] 921 | }, 922 | { 923 | "cell_type": "markdown", 924 | "id": "7c28aadc", 925 | "metadata": {}, 926 | "source": [ 927 | "We see that the performance improvement in `qiskit_alt` is significant, but does not increase with the number of terms `n`. Further benchmarks show that the time required to convert the strings from Python to Julia takes all the time.\n", 928 | "\n", 929 | "We see this in the following." 930 | ] 931 | }, 932 | { 933 | "cell_type": "markdown", 934 | "id": "90790853", 935 | "metadata": {}, 936 | "source": [ 937 | "These last magics (below this version table) are not defined for `juliacall`, so we skip them." 938 | ] 939 | }, 940 | { 941 | "cell_type": "code", 942 | "execution_count": 21, 943 | "id": "4e80561e", 944 | "metadata": {}, 945 | "outputs": [ 946 | { 947 | "name": "stderr", 948 | "output_type": "stream", 949 | "text": [ 950 | "/home/lapeyre/julia_qiskit_repos/qiskit_alt/venv/lib/python3.9/site-packages/qiskit/tools/jupyter/__init__.py:133: RuntimeWarning: matplotlib can't be found, ensure you have matplotlib and other visualization dependencies installed. You can run '!pip install qiskit-terra[visualization]' to install it from jupyter\n", 951 | " warnings.warn(\n" 952 | ] 953 | }, 954 | { 955 | "data": { 956 | "text/html": [ 957 | "

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.0
qiskit_alt0.1.13
qiskit-nature0.3.1
System information
Python version3.9.7
Python compilerGCC 11.1.0
Python builddefault, Oct 10 2021 15:13:22
OSLinux
CPUs12
Memory (Gb)62.76226043701172
Tue Apr 05 11:26:18 2022 EDT
" 958 | ], 959 | "text/plain": [ 960 | "" 961 | ] 962 | }, 963 | "metadata": {}, 964 | "output_type": "display_data" 965 | } 966 | ], 967 | "source": [ 968 | "import qiskit.tools.jupyter\n", 969 | "d = qiskit.__qiskit_version__._version_dict\n", 970 | "d['qiskit_alt'] = qiskit_alt.__version__\n", 971 | "%qiskit_version_table" 972 | ] 973 | }, 974 | { 975 | "cell_type": "code", 976 | "execution_count": 22, 977 | "id": "43d88903", 978 | "metadata": { 979 | "scrolled": true 980 | }, 981 | "outputs": [], 982 | "source": [ 983 | "# %load_ext julia.magic" 984 | ] 985 | }, 986 | { 987 | "cell_type": "markdown", 988 | "id": "0b82727a", 989 | "metadata": {}, 990 | "source": [ 991 | "Generate `1000` ten-qubit Pauli strings." 992 | ] 993 | }, 994 | { 995 | "cell_type": "code", 996 | "execution_count": 23, 997 | "id": "22ac98aa", 998 | "metadata": {}, 999 | "outputs": [], 1000 | "source": [ 1001 | "# %julia using Random: randstring\n", 1002 | "#%julia pauli_strings = [randstring(\"IXYZ\", 10) for _ in 1:1000]\n", 1003 | "# None;" 1004 | ] 1005 | }, 1006 | { 1007 | "cell_type": "markdown", 1008 | "id": "77f21d2d", 1009 | "metadata": {}, 1010 | "source": [ 1011 | "Benchmark converting these to a `QuantumOps.PauliSum`. Note that as the sums are always sorted." 1012 | ] 1013 | }, 1014 | { 1015 | "cell_type": "code", 1016 | "execution_count": 24, 1017 | "id": "4c1dade6", 1018 | "metadata": {}, 1019 | "outputs": [], 1020 | "source": [ 1021 | "# %julia import Pkg; Pkg.add(\"BenchmarkTools\")\n", 1022 | "#%julia using BenchmarkTools: @btime\n", 1023 | "#%julia @btime QuantumOps.PauliSum($pauli_strings)\n", 1024 | "#None;" 1025 | ] 1026 | }, 1027 | { 1028 | "cell_type": "markdown", 1029 | "id": "5b1facf8", 1030 | "metadata": {}, 1031 | "source": [ 1032 | "Check that we are actually getting the right result." 1033 | ] 1034 | }, 1035 | { 1036 | "cell_type": "code", 1037 | "execution_count": 25, 1038 | "id": "ff503a66", 1039 | "metadata": {}, 1040 | "outputs": [], 1041 | "source": [ 1042 | "#%julia pauli_sum = QuantumOps.PauliSum(pauli_strings);\n", 1043 | "#%julia println(length(pauli_sum))\n", 1044 | "#%julia println(pauli_sum[1])" 1045 | ] 1046 | }, 1047 | { 1048 | "cell_type": "code", 1049 | "execution_count": 26, 1050 | "id": "6255a2ef", 1051 | "metadata": {}, 1052 | "outputs": [], 1053 | "source": [ 1054 | "#6.9 * 2.29 / .343 # Ratio of time to construct PauliSum via qiskit_alt to time in pure Julia" 1055 | ] 1056 | }, 1057 | { 1058 | "cell_type": "markdown", 1059 | "id": "d2c36826", 1060 | "metadata": {}, 1061 | "source": [ 1062 | "So the pure Julia code is `46` times faster than the qiskit.quantum_info.\n", 1063 | "**But, the `qiskit.quantum_info` is also extracting a possible phase !**" 1064 | ] 1065 | } 1066 | ], 1067 | "metadata": { 1068 | "kernelspec": { 1069 | "display_name": "qiskit_env_march2022", 1070 | "language": "python", 1071 | "name": "qiskit_env_march2022" 1072 | }, 1073 | "language_info": { 1074 | "codemirror_mode": { 1075 | "name": "ipython", 1076 | "version": 3 1077 | }, 1078 | "file_extension": ".py", 1079 | "mimetype": "text/x-python", 1080 | "name": "python", 1081 | "nbconvert_exporter": "python", 1082 | "pygments_lexer": "ipython3", 1083 | "version": "3.9.7" 1084 | } 1085 | }, 1086 | "nbformat": 4, 1087 | "nbformat_minor": 5 1088 | } 1089 | -------------------------------------------------------------------------------- /demos/qiskit_alt_vqe_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a199fdac", 6 | "metadata": {}, 7 | "source": [ 8 | "

VQE for $H_2$ molecule" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "8a3e139b", 14 | "metadata": {}, 15 | "source": [ 16 | "In this tutorial, we use the Variational Quantum Eigensolver (VQE) algorithm to compute the ground-state energy of the Hydrogen gas molecule ($H_2$).\n", 17 | "Particularly, we use `qiskit-alt` features to perform the Jordan-Wigner transform from the Fermionic operator that describes the $H_2$ molecule to a Pauli operator. " 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "id": "b3b46030", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "from qiskit.algorithms import VQE\n", 28 | "from qiskit.algorithms.optimizers import COBYLA\n", 29 | "from qiskit.opflow import X, Y, Z, I, PauliSumOp\n", 30 | "from qiskit.circuit.library import TwoLocal\n", 31 | "\n", 32 | "from qiskit import Aer\n", 33 | "\n", 34 | "import numpy as np\n", 35 | "\n", 36 | "import qiskit_alt, qiskit_alt.electronic_structure\n", 37 | "from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp\n", 38 | "from qiskit.opflow import PauliSumOp" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "id": "88b64ed3", 44 | "metadata": {}, 45 | "source": [ 46 | "We start by simulating the $H_2$ molecule using the STO-3G basis:" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "id": "7b352a1b", 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "# Describe the Hydrogen molecule by fixing the location of each nucleus\n", 57 | "geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.739]]]\n", 58 | "basis = 'sto3g'" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "c8ace997", 64 | "metadata": {}, 65 | "source": [ 66 | "In order to map the problem to a quantum circuit, we first compute the Fermionic operator of the molecule, then map it to a Pauli operator using the Jordan-Wigner tranform:" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 3, 72 | "id": "4f7a1991", 73 | "metadata": {}, 74 | "outputs": [ 75 | { 76 | "name": "stdout", 77 | "output_type": "stream", 78 | "text": [ 79 | "-0.09577720253672618 * IIII\n", 80 | "- 0.22389389642914415 * ZIII\n", 81 | "+ 0.1715666759109326 * IZII\n", 82 | "+ 0.12068268831019151 * ZZII\n", 83 | "- 0.22389389642914415 * IIZI\n", 84 | "+ 0.17445893297586684 * ZIZI\n", 85 | "+ 0.16597132363812456 * IZZI\n", 86 | "+ 0.04528863532793307 * XXXX\n", 87 | "+ 0.04528863532793307 * YYXX\n", 88 | "+ 0.04528863532793307 * XXYY\n", 89 | "+ 0.04528863532793307 * YYYY\n", 90 | "+ 0.17156667591093255 * IIIZ\n", 91 | "+ 0.16597132363812456 * ZIIZ\n", 92 | "+ 0.16873669065193608 * IZIZ\n", 93 | "+ 0.12068268831019151 * IIZZ\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "# Compute the Fermionic operator of the molecule \n", 99 | "fermi_op = qiskit_alt.electronic_structure.fermionic_hamiltonian(geometry, basis)\n", 100 | "\n", 101 | "# Convert the Fermionic operator to a Pauli operator using the Jordan-Wigner transform\n", 102 | "pauli_op = qiskit_alt.electronic_structure.jordan_wigner(fermi_op);\n", 103 | "\n", 104 | "# Convert the Pauli operator into a sum of Pauli operators\n", 105 | "# input to the VQE algorithm to compute the minimum eigenvalue\n", 106 | "pauli_sum_op = PauliSumOp(pauli_op)\n", 107 | "print(pauli_sum_op)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "981b6ebd", 113 | "metadata": {}, 114 | "source": [ 115 | "We choose the ansatz and the optimizer, and run the the algorithm for 500 shots:" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 4, 121 | "id": "a66e5d69", 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "# Set up the ansatz of type TwoLOcal\n", 126 | "ansatz = TwoLocal(num_qubits=4, \n", 127 | " rotation_blocks=['ry'], \n", 128 | " entanglement_blocks='cx', \n", 129 | " reps=1,\n", 130 | " entanglement='linear', \n", 131 | " skip_final_rotation_layer= False)\n", 132 | "\n", 133 | "# Initialize the COBYLA optimizer\n", 134 | "optimizer = COBYLA(maxiter=500)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "4cb2cca7", 140 | "metadata": {}, 141 | "source": [ 142 | "We select the Aer backend and run the VQE algorithm according to the set parameters. \n", 143 | "We determine the value of the ground-state energy of the Hydrogen molecule as the minimum eigenvalue" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 5, 149 | "id": "cf1cf0bf", 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "name": "stdout", 154 | "output_type": "stream", 155 | "text": [ 156 | "The ground-state energy of the Hydrogen molecule is -1.117 Hatree\n" 157 | ] 158 | } 159 | ], 160 | "source": [ 161 | "# Select the backend for the quantum_instance\n", 162 | "backend = Aer.get_backend('aer_simulator_statevector')\n", 163 | "\n", 164 | "# Run VQE algorithm\n", 165 | "vqe = VQE(ansatz = ansatz, \n", 166 | " optimizer = optimizer,\n", 167 | " quantum_instance = backend)\n", 168 | "\n", 169 | "# Compute the ground-state energy of the molecule\n", 170 | "result = vqe.compute_minimum_eigenvalue(operator=pauli_sum_op)\n", 171 | "print(\"The ground-state energy of the Hydrogen molecule is {} Hatree\".format(round(result.eigenvalue.real,3)))" 172 | ] 173 | } 174 | ], 175 | "metadata": { 176 | "kernelspec": { 177 | "display_name": "Python 3", 178 | "language": "python", 179 | "name": "python3" 180 | }, 181 | "language_info": { 182 | "codemirror_mode": { 183 | "name": "ipython", 184 | "version": 3 185 | }, 186 | "file_extension": ".py", 187 | "mimetype": "text/x-python", 188 | "name": "python", 189 | "nbconvert_exporter": "python", 190 | "pygments_lexer": "ipython3", 191 | "version": "3.8.10" 192 | } 193 | }, 194 | "nbformat": 4, 195 | "nbformat_minor": 5 196 | } 197 | -------------------------------------------------------------------------------- /dev_new.md: -------------------------------------------------------------------------------- 1 | # Working on Julia packages within qiskit_alt 2 | 3 | You can start by using qiskit_alt to set up the Julia project for you. 4 | Then we will forget about qiskit_alt for the time being and only develop 5 | a Julia package. 6 | 7 | ### Optionally install Julia 8 | 9 | You can let qiskit-alt install Julia for you. 10 | But, Julia is easy to install (except for those who run into a problem!); 11 | you may want to try it. 12 | If you do install julia yourself, qiskit-alt will use this installation. 13 | To install Look for either *juliaup* or *jill*. 14 | (Google for *jill python* because there is another non-python version). 15 | For instance to use jill, do this: 16 | ```shell 17 | > pip install jill 18 | > jill install 19 | ``` 20 | This will install julia and try to put it on your command path. 21 | 22 | ### Install qiskit-alt in development mode 23 | 24 | If you are just using qiskit_alt, you can give the command `pip install qiskit_alt`. 25 | You might try this just to explore a bit. 26 | But, since you want to develop qiskit-alt it is better to install it in development mode. 27 | 28 | ```shell 29 | > git clone https://github.com/Qiskit-Extensions/qiskit-alt 30 | > cd qiskit-alt 31 | ``` 32 | 33 | (Really, a better idea is to go to github and fork qiskit-alt, then pull the repostory to 34 | your machine as instructed) 35 | 36 | You typically want to work in a virtual environment that is specific to your development 37 | clone of `qiskit_alt`. So for example in linux (maybe macos too) and 38 | using the standard bash shell, you do (inside the qiskit-alt directory) 39 | 40 | ```shell 41 | > python -m venv ./venv 42 | > source ./venv/bin/activate # Activate this environment 43 | ``` 44 | Just use the command `deactivate` alone to deactivate the environment later. 45 | 46 | Alternatively, you may want to use `conda`; i.e. instead of `venv`. Use only one or the other 47 | at a time. 48 | First install conda. Then do something like 49 | ```shell 50 | > conda create -n qiskit_alt_env python=3.9 51 | > conda activate qiskit_alt_env 52 | ``` 53 | I used python 3.9 here because conda installed 3.10 the first time I tried this. Qiskit 54 | is (probably still?) pegged at python 3.9 because it depends on pyscf which also has this 55 | restriction. 56 | 57 | Now use this shell with the active virtual environment to run your command line interface or Jupyter notebook, or whatever you are going to use. 58 | I use `ipython` as a cli. It has a few good advantages over `python`. With the virtual 59 | environment active, we do 60 | ```shell 61 | > pip install --upgrade pip ipython # upgrade pip to silence complaints 62 | ``` 63 | Now you can install qiskit-alt in development mode like this 64 | ```shell 65 | > pip install -e . 66 | ``` 67 | 68 | Do the first initialization of qiskit-alt. 69 | ```python 70 | > ipython 71 | In [1]: import qiskit_alt 72 | In [2]: qiskit_alt.project.ensure_init(compile=False) 73 | In [3]: %run bench/run_all_bench.py # test some features 74 | ``` 75 | I explicitly used `compile=False` because we want edit packages, which is not possible if they are 76 | compiled into a system image. 77 | 78 | 79 | ### Develop (that is, work on) a Julia package 80 | 81 | You probably only have one version of Julia installed, and it is on your 82 | path, so you can start it by typing `julia` at the shell prompt. 83 | But, it may be that qiskit-alt has installed julia and it is *not* on your 84 | path. (You can try to fix this. I think you may 85 | need to put `~/.local/bin` on your path (`PATH` environment variable) if it is not already. Some OSes 86 | have this on your path already.) 87 | You can also find the path to the julia executable via qiskit-alt like this 88 | ```python 89 | import qiskit_alt 90 | qiskit_alt.project.ensure_init() 91 | qiskit_alt.project.julia_path 92 | ``` 93 | qiskit-alt manages julia dependencies in a *Julia project*, which is similar to 94 | a python virtual environment. You can find the location of the Julia project like this 95 | ```python 96 | qiskit_alt.project.project_path 97 | ``` 98 | If you are using a `venv` virtual environment, this will return a path to the 99 | Julia project that looks something like 100 | ``` 101 | '/home/quser/qiskit_alt/venv/julia_project/qiskit_alt-1.7.2'. 102 | ``` 103 | If you are using conda, this will return a path to the 104 | Julia project that looks something like 105 | ``` 106 | '/home/quser/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2' 107 | ``` 108 | 109 | Start julia from a shell and activate the project and load a Julia package 110 | that is in the project 111 | ```julia 112 | julia> using Pkg 113 | 114 | julia> Pkg.activate("/home/quser/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2") 115 | Activating project at `~/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2` 116 | 117 | julia> using QuantumOps 118 | 119 | julia> rand_op_sum(Pauli, 3, 2) 120 | 2x3 PauliSum{Vector{Vector{Pauli}}, Vector{Complex{Int64}}}: 121 | IXY * (1 + 0im) 122 | YYZ * (1 + 0im) 123 | ``` 124 | 125 | You can check the status of QuantumOps like this 126 | ```julia 127 | julia> pkg"status QuantumOps" 128 | Status `~/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2/Project.toml` 129 | [d0cc4389] QuantumOps v0.1.5 130 | ``` 131 | 132 | The copy of the `QuantumOps` package is read-only (although that's not shown above.) 133 | We need to install an editable copy. So we "develop" the package. This will install 134 | the git repo in a new location where we can edit it. 135 | ```julia 136 | julia> pkg"develop QuantumOps" 137 | Cloning git-repo `https://github.com/Qiskit-Extensions/QuantumOps.jl` 138 | Resolving package versions... 139 | Updating `~/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2/Project.toml` 140 | [d0cc4389] ~ QuantumOps v0.1.5 ⇒ v0.1.5 `~/.julia/dev/QuantumOps` 141 | Updating `~/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2/Manifest.toml` 142 | [d0cc4389] ~ QuantumOps v0.1.5 ⇒ v0.1.5 `~/.julia/dev/QuantumOps` 143 | 144 | julia> pkg"status QuantumOps" 145 | Status `~/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2/Project.toml` 146 | [d0cc4389] QuantumOps v0.1.5 `~/.julia/dev/QuantumOps` 147 | ``` 148 | Note that the status now shows the path in `~/.julia/dev/`. This will persist across 149 | sessions until QuantumOps is freed via `Pkg.free()`. 150 | 151 | Now exit Julia and start again, loading the package Revise first. 152 | ```julia 153 | julia> using Pkg 154 | julia> Pkg.activate("/home/quser/.conda/envs/qiskit_alt_env/julia_project/qiskit_alt-1.7.2"); 155 | julia> using Revise; using QuantumOps; 156 | ``` 157 | 158 | Issue #12 is about printing of Pauli terms. Let's investigate. 159 | ```julia 160 | julia> t = PauliTerm() 161 | 0-factor PauliTerm{Vector{Pauli}, Complex{Int64}}: 162 | * (1 + 0im) 163 | 164 | julia> @which show(stdout, t) 165 | show(io::IO, term::QuantumOps.AbstractTerm) in QuantumOps at /home/quser/.julia/dev/QuantumOps/src/abstract_term.jl:17 166 | ``` 167 | The function `show` is responsible for displaying. And the function method is called is on line 17 of *abstract_term.jl*. 168 | 169 | Let's say we want to print nothing when there are no Pauli operators instead of `* (1 + 0im)`. 170 | The method is [here](https://github.com/Qiskit-Extensions/QuantumOps.jl/blob/d5648bf8779bbe1211bd5c63270bad165384e344/src/abstract_term.jl#L7-L21) 171 | ```julia 172 | function Base.show(io::IO, term::AbstractTerm) 173 | m = length(term) 174 | print(io, m, "-factor", " ", typeof(term), ":\n") 175 | _show_abstract_term(io, term) 176 | end 177 | ``` 178 | Edit the function to read 179 | ```julia 180 | function Base.show(io::IO, term::AbstractTerm) 181 | m = length(term) 182 | if m == 0 183 | reuturn nothing 184 | end 185 | ... 186 | ``` 187 | Now return to your Julia session and ask to display the term again, and you 188 | will see it is no longer printed. The package Revise watches the source for changes 189 | and recompiles only the changed code. 190 | ```julia 191 | julia> t 192 | 0-factor PauliTerm{Vector{Pauli}, Complex{Int64}}: 193 | ``` 194 | This may not actually be the best way to deal with this problem. But it illustrates 195 | the process. If needed, you can fork QuantumOps.jl on github and add the fork as a 196 | `remote` in the git repository `~/.julia/dev/QuantumOps`. You can then push a PR. 197 | -------------------------------------------------------------------------------- /docker_tests/Dockerfile: -------------------------------------------------------------------------------- 1 | # I used fedora:34 rather than fedora:35 as the base image because of 2 | # https://bugzilla.redhat.com/show_bug.cgi?id=1988199. 3 | # Comment 26 there points to an upstream RHEL bug, but I am not 4 | # certain it is the same bug as I was experiencing [garrison]. 5 | FROM fedora:34 6 | 7 | ### Install all needed packages from distribution and remove the package download cache. 8 | RUN dnf install -y git @development-tools gcc-c++ python3 python3-devel passwd fish emacs wget && \ 9 | dnf clean dbcache 10 | 11 | # Is the followig necessary ? 12 | RUN python3 -m pip install -U pip 13 | 14 | ### Create a user 'quser' and add to sudoers 15 | 16 | RUN useradd --create-home --shell /bin/bash quser && echo "quser:quser" | chpasswd && \ 17 | usermod -aG wheel quser && mkdir /home/quser/qiskit_alt && chown -R quser /home/quser && chgrp -R quser /home/quser 18 | 19 | ### Change quser 20 | 21 | WORKDIR /home/quser/qiskit_alt 22 | USER quser 23 | 24 | ### Create a virtual envirnoment venv1, install jill.py, and install julia via jill.py 25 | 26 | RUN python3 -m venv ./venv1 && source ./venv1/bin/activate && pip install --upgrade pip jill && \ 27 | jill install --confirm 28 | # Note 'printf "y\n" | jill install' works at the cli, but not here :( 29 | 30 | ### Install all python packges needed for qiskit_alt in venv1, but not qiskit_alt. 31 | 32 | COPY --chown=quser requirements.txt . 33 | RUN source ./venv1/bin/activate && pip install ipython && \ 34 | pip install --requirement requirements.txt 35 | 36 | ### Do system-wide install of miniconda 37 | 38 | SHELL ["/bin/bash", "--login", "-c"] 39 | 40 | USER root 41 | WORKDIR /root 42 | # Install miniconda 43 | ENV CONDA_DIR /opt/conda 44 | ENV PATH=$CONDA_DIR/bin:$PATH 45 | RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \ 46 | /bin/bash ~/miniconda.sh -b -p /opt/conda 47 | 48 | RUN conda init bash && conda init fish && conda config --set auto_activate_base false 49 | 50 | ### Setup conda for user quser 51 | 52 | USER quser 53 | WORKDIR /home/quser/qiskit_alt 54 | 55 | ## Create a conda environment called qiskit_alt_env 56 | 57 | RUN printf "y\n" | conda create -n qiskit_alt_env python=3.9 58 | RUN conda init bash && conda init fish && conda config --set auto_activate_base false 59 | 60 | ## Use conda-installed python to create a venv virtual environment. This venv is 61 | ## unrelated to conda. But, we do it because the conda-installed python is statically 62 | ## linked to libpython. This allows us to test both statically and dynamically linked 63 | ## python. 64 | 65 | RUN /opt/conda/bin/python -m venv ./venv_static && \ 66 | source ./venv_static/bin/activate && pip install --upgrade pip ipython && \ 67 | pip install --requirement requirements.txt 68 | 69 | RUN conda activate qiskit_alt_env && pip install ipython && pip install --requirement requirements.txt 70 | 71 | ### Copy all and only files required for pip install of qiskit_alt in dev mode 72 | 73 | COPY --chown=quser requirements.txt setup.cfg pyproject.toml README.md ./ 74 | COPY --chown=quser src ./src/ 75 | 76 | ### Install qiskit_alt into the three virtual environments we have created. 77 | 78 | COPY --chown=quser requirements_in_project.txt . 79 | RUN source ./venv1/bin/activate && \ 80 | pip install --requirement requirements_in_project.txt 81 | 82 | RUN /opt/conda/bin/python -m venv ./venv_static && \ 83 | source ./venv_static/bin/activate && \ 84 | pip install --requirement requirements_in_project.txt 85 | 86 | RUN conda activate qiskit_alt_env && pip install --requirement requirements_in_project.txt 87 | 88 | RUN source ./venv1/bin/activate && pip install -e . 89 | RUN source ./venv_static/bin/activate && pip install -e . 90 | RUN conda activate qiskit_alt_env && pip install -e . 91 | 92 | ### Copy files used to test initializing qiskit_alt. That is downloading Julia packages, etc. 93 | 94 | COPY --chown=quser init_test.py run_init_tests.sh ./ 95 | 96 | ### Start container as root user by default. 97 | ### This is less necessary now the we put quser in sudoers group. 98 | 99 | USER root 100 | WORKDIR /root 101 | 102 | # Put our fake .juliaup directory on the PATH. We will test that it 103 | # is rejected by find_julia 104 | ENV PATH=/home/quser/.juliaup/bin:$PATH 105 | 106 | # Set up the fake .juliaup directory and make /usr/bin/julia a symlink 107 | # These locations are rejected by find_julia 108 | # However /usr/bin/julia won't be chosen anyway as there are other julias 109 | # earlier on the path. It will take more work (manipulating PATH) to test this. 110 | RUN ln -s /home/quser/packages/julias/julia-1.7/bin/julia /usr/bin/julia && \ 111 | mkdir /home/quser/.juliaup && mkdir /home/quser/.juliaup/bin && \ 112 | echo "#" > /home/quser/.juliaup/bin/julialauncher && \ 113 | chmod +x /home/quser/.juliaup/bin/julialauncher && \ 114 | ln -s /home/quser/.juliaup/bin/julialauncher /home/quser/.juliaup/bin/julia 115 | -------------------------------------------------------------------------------- /docker_tests/README-docker_tests.md: -------------------------------------------------------------------------------- 1 | #### Testing installation with docker 2 | 3 | First install docker. We don't cover how to do that here. 4 | 5 | Use 6 | ```shell 7 | run_dockerfile.sh build 8 | ``` 9 | to build the docker image. 10 | Use 11 | ```shell 12 | run_dockerfile.sh run 13 | ``` 14 | to run installation tests. 15 | Use 16 | ```shell 17 | run_dockerfile.sh 18 | ``` 19 | to first build, then run. 20 | Use 21 | ```shell 22 | run_dockerfile.sh fish 23 | ``` 24 | for an interactive fish shell. 25 | Use 26 | ```shell 27 | run_dockerfile.sh bash 28 | ``` 29 | for an interactive bash shell. 30 | 31 | ```shell 32 | run_dockerfile.sh rootfish 33 | ``` 34 | for an interactive fish shell as root 35 | 36 | 37 | #### 'Manual' building and running 38 | 39 | You can build the docker image by changing to the `./docker_tests` directory and doing 40 | ```shell 41 | docker build -t qiskit_alt -f Dockerfile .. | tee dockerfile.log 42 | ``` 43 | You can run some installation tests like this 44 | ```shell 45 | docker run -it qiskit_alt:latest /usr/bin/su -l quser -c "cd qiskit_alt; sh ./run_init_tests.sh" 46 | ``` 47 | 48 | #### Working in the docker container 49 | 50 | To experiment with the docker container you can do 51 | ```shell 52 | docker run -it qiskit_alt /usr/bin/su -l quser -s /usr/bin/fish 53 | ``` 54 | This example uses the fish shell. 55 | -------------------------------------------------------------------------------- /docker_tests/run_dockerfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | r""" 4 | This is a test docstring. 5 | """ 6 | 7 | import subprocess as sb_pr 8 | import fire 9 | 10 | 11 | def subprocess_execute(command_list): 12 | """Subprocess_execute executes the command on host OS, 13 | then dumps the output to STDOUT. 14 | Arguments: 15 | command_list: This is a list of string making the command to be executed. 16 | """ 17 | sb_pr.run(command_list, text=True, check=True) 18 | 19 | 20 | def action_build(image_name, image_tag, dockerfile_name, docker_path): 21 | """The function action_build builds the image 22 | Arguments: 23 | image_name: Name of the image file. 24 | image_tag: Tag of the build image file. 25 | dockerfile_name: This is the Dockerfile to be used for building the image. 26 | docker_path: working directory of docker. 27 | """ 28 | 29 | image_name_with_tag = image_name + ":" + image_tag 30 | build_command_list = [ 31 | "docker", 32 | "build", 33 | "-t", 34 | image_name_with_tag, 35 | "-f", 36 | dockerfile_name, 37 | docker_path, 38 | ] 39 | return build_command_list 40 | 41 | 42 | def action_run(image_name, image_tag, user_name, test_file_path, test_file_name): 43 | """The function action_run runs the container and initiates the tests. 44 | Arguments: 45 | image_name: Name of image to be used to build the containers. 46 | image_tag: The tag of imgaes to be used to build the containers. 47 | test_file_path: Path of the test file from which tests has to run. 48 | test_file_name: Name of the file containing the tests to be done. 49 | """ 50 | 51 | image_name_with_tag = image_name + ":" + image_tag 52 | run_command_list = [ 53 | "docker", 54 | "run", 55 | "-it", 56 | image_name_with_tag, 57 | "/usr/bin/su", 58 | "-l", 59 | user_name, 60 | "-c", 61 | "cd qiskit_alt; sh " + test_file_path + test_file_name, 62 | ] 63 | return run_command_list 64 | 65 | 66 | def action_get_into_fish(image_name, image_tag, user_name): 67 | """The function action_get_into_fish takes into the fish shell running in the container. 68 | Arguments: 69 | image_name: Name of the image to be used to build the container. 70 | image_tag: The tag of the image which is used to build the container. 71 | user_name: The user name which logins into the container. 72 | """ 73 | 74 | image_name_with_tag = image_name + ":" + image_tag 75 | get_fish_command_list = [ 76 | "docker", 77 | "run", 78 | "-it", 79 | image_name_with_tag, 80 | "/usr/bin/su", 81 | "-l", 82 | user_name, 83 | "-s", 84 | "/usr/bin/fish", 85 | ] 86 | return get_fish_command_list 87 | 88 | 89 | def action_get_into_bash(image_name, image_tag, user_name): 90 | """The function action_get_into_bash takes into the bash shell running in the container. 91 | Arguments: 92 | image_name: Name of the image to be used to build the container. 93 | image_tag: The tag of the image which is used to build the container. 94 | user_name: The user name which logins into the container. 95 | """ 96 | 97 | image_name_with_tag = image_name + ":" + image_tag 98 | get_bash_command_list = [ 99 | "docker", 100 | "run", 101 | "-it", 102 | image_name_with_tag, 103 | "/usr/bin/su", 104 | "-l", 105 | user_name, 106 | "-s", 107 | "/usr/bin/bash", 108 | ] 109 | return get_bash_command_list 110 | 111 | 112 | def action_get_into_rootfish(image_name, image_tag): 113 | """The function action_get_into_rootfish takes into the fish shell 114 | running in the container as root. 115 | Arguments: 116 | image_name: Name of the image to be used to build the container. 117 | image_tag: The tag of the image which is used to build the container. 118 | user_name: The user name which logins into the container. 119 | """ 120 | 121 | image_name_with_tag = image_name + ":" + image_tag 122 | get_rootfish_command_list = [ 123 | "docker", 124 | "run", 125 | "-it", 126 | image_name_with_tag, 127 | "/usr/bin/fish", 128 | ] 129 | return get_rootfish_command_list 130 | 131 | 132 | def _cli( 133 | action="", 134 | image_name="qiskit_alt", 135 | image_tag="latest", 136 | dockerfile_name="Dockerfile", 137 | user_name="quser", 138 | test_file_path="./", 139 | test_file_name="run_init_tests.sh", 140 | docker_path="..", 141 | dry_run="false", 142 | ): 143 | """All the arguments of this function are supposed to be passed as command line 144 | arguments while initiating the python script. 145 | Arguments: 146 | action: This are the possible actions to be performed. 147 | Possible actions are: 148 | build: To build the containers 149 | Example: ./run_dockerfile.py --action=build 150 | run: To run the containers 151 | "": To build and then to run the containers. 152 | get_into_fish: To get into the fish shell running in the container. 153 | get_into_bash: To get into the bash shell running in the container. 154 | get_into_rootfish: To get into the fish shell running in the container 155 | as root. 156 | 157 | image_name: The name of the image to be build. 158 | image_tag: The tag given to the image to be build. 159 | dockerfile_name: The name of the Dockerfile to be used for building the image. 160 | user_name: A username in the container. 161 | test_file_path: The path to the test file which contains all the tests to run. 162 | docker_path: The working directory for docker. 163 | dry_run: Either true or false. If true, then only print action, but don't execute it. 164 | """ 165 | 166 | if dry_run == "false": 167 | _dry_run = False 168 | elif dry_run == "true": 169 | _dry_run = True 170 | else: 171 | print("dry_run must be either true or false. See ./run_dockerfile.py --help") 172 | return 173 | 174 | command_lists = [] 175 | 176 | if action == "build": 177 | command_lists.append(action_build(image_name, image_tag, dockerfile_name, docker_path)) 178 | elif action == "run": 179 | command_lists.append(action_run(image_name, image_tag, user_name, test_file_path, test_file_name)) 180 | elif action == "": 181 | command_lists.append(action_build(image_name, image_tag, dockerfile_name, docker_path)) 182 | command_lists.append(action_run(image_name, image_tag, user_name, test_file_path, test_file_name)) 183 | elif action == "get_into_fish": 184 | command_lists.append(action_get_into_fish(image_name, image_tag, user_name)) 185 | elif action == "get_into_bash": 186 | command_lists.append(action_get_into_bash(image_name, image_tag, user_name)) 187 | elif action == "get_into_rootfish": 188 | command_lists.append(action_get_into_rootfish(image_name, image_tag)) 189 | else: 190 | print("Bad arguments, See ./run_dockerfile.py --help") 191 | 192 | for command_list in command_lists: 193 | command_string = " ".join(map(str, command_list)) 194 | print(command_string + "\n") 195 | if not _dry_run: 196 | subprocess_execute(command_list) 197 | 198 | 199 | if __name__ == "__main__": 200 | fire.Fire(_cli) 201 | -------------------------------------------------------------------------------- /docker_tests/run_dockerfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD="docker build -t qiskit_alt -f Dockerfile .." 4 | 5 | if [[ $1 == "build" ]] 6 | then 7 | echo $BUILD 8 | $BUILD 9 | elif [[ $1 == "run" ]] 10 | then 11 | echo 'docker run -it qiskit_alt:latest /usr/bin/su -l quser -c "cd qiskit_alt; sh ./run_init_tests.sh"' 12 | docker run -it qiskit_alt:latest /usr/bin/su -l quser -c "cd qiskit_alt; sh ./run_init_tests.sh" 13 | elif [[ $1 == "" ]] 14 | then 15 | $BUILD 16 | echo 'docker run -it qiskit_alt:latest /usr/bin/su -l quser -c "cd qiskit_alt; sh ./run_init_tests.sh"' 17 | docker run -it qiskit_alt:latest /usr/bin/su -l quser -c "cd qiskit_alt; sh ./run_init_tests.sh" 18 | elif [[ $1 == "fish" ]] 19 | then 20 | docker run -it qiskit_alt /usr/bin/su -l quser -s /usr/bin/fish 21 | elif [[ $1 == "bash" ]] 22 | then 23 | docker run -it qiskit_alt /usr/bin/su -l quser -s /usr/bin/bash 24 | elif [[ $1 == "rootfish" ]] 25 | then 26 | docker run -it qiskit_alt /usr/bin/fish 27 | else 28 | echo Excpecting argument "build" or "run", got $1 29 | echo "'run_dockerfile.sh build' to build image" 30 | echo "'run_dockerfile.sh run' to run tests in container" 31 | echo "'run_dockerfile.sh' to build, then run." 32 | echo "'run_dockerfile.sh fish' for an interactive fish shell" 33 | echo "'run_dockerfile.sh bash' for an interactive bash shell" 34 | fi 35 | -------------------------------------------------------------------------------- /examples/h2_hamiltonian_alt.py: -------------------------------------------------------------------------------- 1 | import qiskit_alt 2 | geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.7414]]] 3 | #basis = 'sto3g' 4 | basis = '631++g' 5 | 6 | fermi_op = qiskit_alt.fermionic_hamiltonian(geometry, basis) 7 | pauli_op = qiskit_alt.jordan_wigner(fermi_op) 8 | 9 | #basis = '631g' 10 | #basis = 'dzvp' 11 | 12 | # Too big 13 | #basis = 'dzp' 14 | #basis = 'dzvp2' 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/jw_example.jl: -------------------------------------------------------------------------------- 1 | # # Jordan-Wigner transform 2 | 3 | ENV["PYCALL_JL_RUNTIME_PYTHON"] = Sys.which("python") 4 | 5 | # Load the interface to electronic structure packages. 6 | # As with Qiskit and OpenFermion driver libraries, `ElectronicStructure` does 7 | # a bit of calculation, as well. 8 | using ElectronicStructure 9 | 10 | # Load `QuantumOps`, the implementation of Pauli and Fermionc operators 11 | using QuantumOps 12 | 13 | # `ElectronicStructure` has no hard dependency on pyscf. 14 | # But, using `PyCall` will trigger loading pyscf-specific code in `ElectronicStructure` 15 | using PyCall 16 | 17 | # Load some specific identifiers 18 | using ElectronicStructure: one_electron_integrals, MolecularData, PySCF 19 | 20 | # ## Specify geometry of molecules 21 | 22 | # Define geometries for three molecules: specification of atoms and their positions. 23 | # These are molecular hydrogen, `LiH`, and water. 24 | geoms = ( 25 | Geometry(Atom(:H, (0., 0., 0.)), Atom(:H, (0., 0., 0.7414))), 26 | 27 | Geometry(Atom(:Li, (0., 0., 0.)), Atom(:H, (0., 0., 1.4))), 28 | 29 | Geometry(Atom(:O, (0., 0., 0.)), Atom(:H, (0.757, 0.586, 0.)), 30 | Atom(:H, (-0.757, 0.586, 0.))) 31 | ); 32 | 33 | # Choose one of the geometries, 1, 2, or 3. 34 | geom = geoms[3] 35 | 36 | # Choose a orbital basis set. 37 | # basis = "sto-3g"; 38 | basis = "631g" 39 | 40 | # We have chosen the crudest basis set and the smallest molecule. Otherwise 41 | # the size of the data structures is too large to display in a demonstration. 42 | 43 | # ## Compute interaction integrals for the chosen molecule and basis set 44 | 45 | # Construct the specification of the electronic structure problem. 46 | mol_spec = MolecularSpec(geometry=geom, basis=basis) 47 | 48 | # Do calculations using `PySCF` and populate a `MolecularData` object with results. 49 | # `mol_pyscf` holds a constant, a rank-two tensor, and a rank-four tensor. 50 | mol_pyscf = MolecularData(PySCF, mol_spec); 51 | 52 | # ## Include spin orbitals and change the representation 53 | 54 | # Create an interaction operator from one- and two-body integrals and a constant. 55 | # This does just a bit of manipulation of `mol_data`; converting space orbitals 56 | # into space-and-spin orbitals. There are options for choosing chemists' or physicists' 57 | # index ordering and block- or inteleaved-spin orbital ordering. 58 | # We take the default here, 59 | # which gives the same as the operator by the same name in OpenFermion. 60 | # The data is still in the form of rank two and four tensors, but the size of each 61 | # dimension is doubled. 62 | iop = InteractionOperator(mol_pyscf); 63 | 64 | # Now we convert `iop` to a more-sparse format; a `FermiSum` (alias for `OpSum{FermiOp}`). 65 | # This is sparse in the sense that only non-zero entries in the tensors in `iop` are represented. 66 | # However, it is not as sparse as it could be, in that identity operators on modes are represented 67 | # explicitly. 68 | fermi_op = FermiSum(iop) 69 | 70 | # ## Jordan-Wigner transform 71 | 72 | using QuantumOps: jordan_wigner 73 | 74 | # Compute the Jordan-Wigner transform 75 | pauli_op = jordan_wigner(fermi_op) 76 | 77 | nothing; 78 | -------------------------------------------------------------------------------- /examples/jw_example.py: -------------------------------------------------------------------------------- 1 | import qiskit_alt 2 | 3 | def do_jw_problem(): 4 | # qiskit_alt.Main.eval('include("examples/jw_example.jl")') 5 | qiskit_alt.Main.eval('include("jw_example.jl")') 6 | pauli_op = qiskit_alt.Main.eval("pauli_op") 7 | spop_jl = qiskit_alt.QiskitQuantumInfo.SparsePauliOp(pauli_op) 8 | spop = qiskit_alt.jlSparsePauliOp(spop_jl) 9 | return spop 10 | -------------------------------------------------------------------------------- /examples/nature_qubit_hamiltonian_ex.py: -------------------------------------------------------------------------------- 1 | from qiskit_nature.drivers import UnitsType, Molecule 2 | from qiskit_nature.drivers.second_quantization import ElectronicStructureDriverType, ElectronicStructureMoleculeDriver 3 | 4 | # geometry = [['H', [0., 0., 0.]], 5 | # ['H', [0., 0., 0.735]]] 6 | 7 | geometry = [['H', [0., 0., 0.]], 8 | ['H', [0., 0., 0.7414]]] # same as used in QuantuOps.jl example 9 | 10 | 11 | # geometry = [['O', [0., 0., 0.]], 12 | # ['H', [0.757, 0.586, 0.]], 13 | # ['H', [-0.757, 0.586, 0.]]] 14 | 15 | 16 | basis = 'sto3g' 17 | #basis = '631g' 18 | #basis = 'dzvp2' 19 | 20 | molecule = Molecule(geometry=geometry, 21 | charge=0, multiplicity=1) 22 | driver = ElectronicStructureMoleculeDriver(molecule, basis=basis, driver_type=ElectronicStructureDriverType.PYSCF) 23 | 24 | from qiskit_nature.problems.second_quantization import ElectronicStructureProblem 25 | from qiskit_nature.converters.second_quantization import QubitConverter 26 | from qiskit_nature.mappers.second_quantization import JordanWignerMapper 27 | 28 | es_problem = ElectronicStructureProblem(driver) 29 | second_q_op = es_problem.second_q_ops() 30 | 31 | fermionic_hamiltonian = second_q_op[0] 32 | 33 | qubit_converter = QubitConverter(mapper=JordanWignerMapper()) 34 | nature_qubit_op = qubit_converter.convert(fermionic_hamiltonian) 35 | -------------------------------------------------------------------------------- /examples/qubit_hamiltonian_ex.py: -------------------------------------------------------------------------------- 1 | import qiskit_alt 2 | 3 | #geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.7414]]] 4 | 5 | geometry = [['O', [0., 0., 0.]], 6 | ['H', [0.757, 0.586, 0.]], 7 | ['H', [-0.757, 0.586, 0.]]] 8 | 9 | #basis = 'sto3g' 10 | #basis = '631g' 11 | basis = 'dzvp2' 12 | 13 | pauli_op = qiskit_alt.jordan_wigner(geometry, basis) 14 | -------------------------------------------------------------------------------- /init_test.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | def python_commands(commands): 4 | try: 5 | result = subprocess.run( 6 | ['python', '-c', commands], check=True, capture_output=True, encoding='utf8' 7 | ) 8 | except subprocess.CalledProcessError as err: 9 | return err 10 | return result 11 | 12 | 13 | def basic_inits(): 14 | all_coms = [] 15 | for _compile in ("False", "True"): 16 | for calljulia in ("pyjulia", "juliacall"): 17 | for depot in ("False", "True"): 18 | args = f"compile={_compile}, calljulia='{calljulia}', depot={depot}" 19 | coms = f"import qiskit_alt; qiskit_alt.project.ensure_init({args})" 20 | all_coms.append(coms) 21 | if calljulia == "pyjulia": 22 | other_calljulia = "juliacall" 23 | else: 24 | other_calljulia = "pyjulia" 25 | args = f"compile={_compile}, calljulia='{other_calljulia}', depot={depot}" 26 | coms = f"import qiskit_alt; qiskit_alt.project.ensure_init({args}); qiskit_alt.project.clean_all()" 27 | all_coms.append(coms) 28 | return all_coms 29 | 30 | # def basic_inits(): 31 | # all_coms = ["import sys", "import os", "import sdsdff", "import sdsdff", "import shutil"] 32 | # return all_coms 33 | 34 | 35 | def run_tests(all_commands=None, verbose=False): 36 | num_passed = 0 37 | if all_commands is None: 38 | all_commands = basic_inits() 39 | for commands in all_commands: 40 | print(f"running '{commands}'") 41 | result = python_commands(commands) 42 | if isinstance(result, subprocess.CalledProcessError): 43 | print(f"**** Commands '{commands}' failed with error code {result}") 44 | print(result.stderr) 45 | else: 46 | num_passed += 1 47 | if verbose: 48 | print(result) 49 | msg = f"{num_passed} of {len(all_commands)} installation tests passed" 50 | if num_passed < len(all_commands): 51 | print("**** " + msg) 52 | else: 53 | print(msg) 54 | 55 | 56 | if __name__ == '__main__': 57 | run_tests() 58 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qiskit-alt/77cf546db0d5f014b50bcf742bed3c99df87e346/pyproject.toml -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest>=6.2.5 2 | fire>=0.4.0 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | julia>=0.5.7 2 | juliacall 3 | pyscf>=2.0.1 4 | qiskit-terra>=0.19.0 5 | qiskit-nature>=0.2.2 6 | ipywidgets 7 | -------------------------------------------------------------------------------- /requirements_in_project.txt: -------------------------------------------------------------------------------- 1 | julia_project>=0.1.27 2 | 3 | -------------------------------------------------------------------------------- /run_init_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | # Must deactivate to avoid complaint about mixed virtual environments 4 | echo "source ./venv1/bin/activate && python init_test.py" 5 | source ./venv1/bin/activate && python init_test.py 6 | echo 7 | deactivate 8 | 9 | echo "source ./venv_static/bin/activate && python init_test.py" 10 | source ./venv_static/bin/activate && python init_test.py 11 | echo 12 | deactivate 13 | 14 | # Work around poor design 15 | CONDA_BASE=$(conda info --base) 16 | source $CONDA_BASE/etc/profile.d/conda.sh 17 | echo "conda activate qiskit_alt_env && python init_test.py" 18 | conda activate qiskit_alt_env && python init_test.py 19 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = qiskit_alt 3 | version = attr: qiskit_alt.__version__ 4 | description = Julia backend for Qiskit 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | license = Apache 2.0 8 | classifiers = 9 | Intended Audience :: Science/Research 10 | License :: OSI Approved :: Apache Software License 11 | Programming Language :: Python :: 3 12 | url = http://github.com/Qiskit-Extensions/qiskit-alt 13 | project_urls = 14 | Bug Tracker = https://github.com/Qiskit-Extensions/qiskit-alt/issues 15 | Source Code = https://github.com/Qiskit-Extensions/qiskit-alt 16 | 17 | 18 | [options] 19 | zip_safe = False 20 | packages = find: 21 | package_dir = 22 | =src 23 | 24 | 25 | # ipywidgets is pretty heavy, and only used for version table in notebooks 26 | install_requires = 27 | pyscf >= 2.0 28 | julia 29 | juliacall 30 | qiskit-terra >= 0.19 31 | qiskit-nature >= 0.2 32 | julia_project >= 0.1.26 33 | ipywidgets 34 | 35 | 36 | [options.packages.find] 37 | where = src 38 | 39 | [tool:pytest] 40 | norecursedirs = .* scripts build dist 41 | 42 | # https://github.com/pytest-dev/pytest/issues/1445 43 | [easy_install] 44 | zip_ok = 0 45 | 46 | [options.package_data] 47 | qiskit_alt = sys_image/packages.jl 48 | sys_image/Project.toml 49 | sys_image/compile_exercise_script.jl 50 | Project.toml 51 | electronic_structure.jl 52 | es_juliacall.jl 53 | juliacall_util.jl 54 | -------------------------------------------------------------------------------- /src/qiskit_alt/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | ElectronicStructure = "f7ec468b-4310-4c77-90aa-f73697106762" 3 | QiskitQuantumInfo = "8d55b643-9899-48dd-ae5e-3f792c55f725" 4 | QuantumOps = "d0cc4389-ef80-471f-8a8c-dd305d114ada" 5 | Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" 6 | ZChop = "8603256b-76ad-53fe-b511-38a38e6437cd" 7 | 8 | [compat] 9 | ElectronicStructure = ">= 0.1.8" 10 | ZChop = ">= 0.3.10" 11 | QuantumOps = ">= 0.1.5" 12 | -------------------------------------------------------------------------------- /src/qiskit_alt/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.14" 2 | 3 | from .julia_project import project 4 | 5 | # QuantumOps = project.simple_import("QuantumOps") 6 | # from julia import QuantumOps, QiskitQuantumInfo, ElectronicStructure 7 | # from .pauli_operators import jlPauli, jlSparsePauliOp, PauliSum_to_SparsePauliOp 8 | # from .electronic_structure import Geometry, jordan_wigner, fermionic_hamiltonian 9 | -------------------------------------------------------------------------------- /src/qiskit_alt/e_struct_julia.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .julia_project import project 3 | project.ensure_init() 4 | from .pauli_operators import jlSparsePauliOp 5 | 6 | from . import e_struct_python 7 | 8 | Main = project.simple_import("Main") 9 | QuantumOps = project.simple_import("QuantumOps") 10 | QiskitQuantumInfo = project.simple_import("QiskitQuantumInfo") 11 | ElectronicStructure = project.simple_import("ElectronicStructure") 12 | 13 | 14 | def fermionic_hamiltonian(geometry, basis): 15 | """ 16 | Given a molecular geometry specification and basis set, return 17 | the electronic Hamiltonian as a QuantumOps.FermiSum. 18 | 19 | The integrals are computed by pyscf. The geometry spec may be in either qiskit-nature 20 | format or qiskit_alt format. 21 | """ 22 | mol_data = e_struct_python.MolecularData.from_specs(geometry=geometry, basis=basis) 23 | iop = ElectronicStructure.interaction_operator(mol_data.nuclear_repulsion, 24 | mol_data.one_body_integrals, 25 | mol_data.two_body_integrals) 26 | return QuantumOps.FermiSum(iop) 27 | 28 | 29 | def jordan_wigner(fermi_op): 30 | """ 31 | Compute the Jordan-Wigner transform of a QuantumOps.FermiSum representing a Fermionic Hamiltonian. 32 | Return a qiskit.SparsePauliOp. 33 | """ 34 | pauli_op = QuantumOps.jordan_wigner(fermi_op) 35 | spop_jl = QiskitQuantumInfo.SparsePauliOp(pauli_op) # Convert to QiskitQuantumInfo.SparsePauliOp 36 | sparse_pauli = jlSparsePauliOp(spop_jl) # Convert to qisit.quantum_info.SparsePauliOp 37 | return sparse_pauli 38 | -------------------------------------------------------------------------------- /src/qiskit_alt/e_struct_python.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import functools 3 | import pyscf 4 | import numpy 5 | 6 | """ 7 | Pure-Python electronic structure code. This mostly provides an interface to PySCF. 8 | """ 9 | 10 | 11 | Atom = namedtuple("Atom", "species coords") 12 | 13 | 14 | def atom(species, coords): 15 | if len(coords) != 3: 16 | raise ValueError(f"coords must be a three-tuple of floats. Got {coords}") 17 | return Atom(species, coords) 18 | 19 | 20 | def atom_to_pyscf(atom): 21 | if not isinstance(atom, Atom): 22 | raise TypeError(f"Expect type `Atom`, got {type(atom)}'") 23 | return atom.species + " " + " ".join((str(coord) for coord in atom.coords)) 24 | 25 | 26 | Geometry = namedtuple("Geometry", "atoms") 27 | 28 | 29 | def geometry(*atoms): 30 | if _is_nature_geometry(atoms[0]): 31 | return geometry_from_nature(atoms[0]) 32 | if not all(isinstance(atom, Atom) for atom in atoms): 33 | raise ValueError(f"geometry: all arguments must be of type Atom") 34 | return Geometry(atoms) 35 | 36 | 37 | def _is_nature_geometry(geom): 38 | return (isinstance(geom, list) and 39 | all((isinstance(g, list) and len(g) == 2 and 40 | isinstance(g[0], str) and isinstance(g[1], list) and 41 | len(g[1]) == 3) for g in geom) 42 | ) 43 | 44 | 45 | def geometry_from_nature(geom_list): 46 | if not _is_nature_geometry(geom_list): 47 | raise ValueError(f"{geom_list} is not a valid geometry specification") 48 | return Geometry([Atom(a[0], a[1]) for a in geom_list]) 49 | 50 | 51 | def geometry_to_pyscf(geometry): 52 | return ";".join((atom_to_pyscf(atom) for atom in geometry.atoms)) 53 | 54 | 55 | class MolecularSpec: 56 | def __init__(self, _geometry, multiplicity=None, charge=None, basis=None): 57 | self.geometry = geometry(_geometry) 58 | self.multiplicity = 1 if multiplicity is None else multiplicity 59 | self.charge = 0 if charge is None else charge 60 | self.basis = 'sto-3g' if basis is None else basis # probably should be required 61 | 62 | 63 | @property 64 | def spin(self): 65 | return self.multiplicity - 1 66 | 67 | 68 | def to_pyscf(self): 69 | pymol = pyscf.gto.Mole(atom=geometry_to_pyscf(self.geometry), basis=self.basis) 70 | pymol.spin = self.spin 71 | pymol.charge = self.charge 72 | pymol.symmetry = False 73 | pymol.build() 74 | return pymol 75 | 76 | 77 | def one_electron_integrals(scf): 78 | mo_coeff = scf.mo_coeff 79 | h_core = scf.get_hcore() 80 | return functools.reduce(numpy.dot, (mo_coeff.T, h_core, mo_coeff)) 81 | 82 | 83 | def two_electron_integrals(pymol, scf): 84 | two_electron_compressed = pyscf.ao2mo.kernel(pymol, scf.mo_coeff) 85 | n_orbitals = scf.mo_coeff.shape[1] 86 | symmetry_code = 1 # No permutation symmetry 87 | return pyscf.ao2mo.restore(symmetry_code, two_electron_compressed, n_orbitals) 88 | 89 | 90 | class MolecularData: 91 | def __init__(self, 92 | spec, 93 | nuclear_repulsion, 94 | one_body_integrals, 95 | two_body_integrals 96 | ): 97 | self.spec = spec 98 | self.nuclear_repulsion = nuclear_repulsion 99 | self.one_body_integrals = one_body_integrals 100 | self.two_body_integrals = two_body_integrals 101 | 102 | 103 | @classmethod 104 | def using_pyscf(cls, mol_spec : MolecularSpec): 105 | pymol = mol_spec.to_pyscf() 106 | if pymol.spin != 0: # from OpenFermion 107 | scf = pyscf.scf.ROHF(pymol) 108 | else: 109 | scf = pyscf.scf.RHF(pymol) 110 | 111 | # Run Hartree-Fock 112 | verbose = scf.verbose 113 | scf.verbose = 0 114 | scf.run() 115 | scf.verbose = verbose 116 | one_e_ints = one_electron_integrals(scf) # Compute one and two body integrals 117 | two_e_ints = two_electron_integrals(pymol, scf) 118 | nuclear_repulsion = float(pymol.energy_nuc()) # Compute constant energy, convert from numpy.float64 to float 119 | return MolecularData(mol_spec, nuclear_repulsion, one_e_ints, two_e_ints) 120 | 121 | 122 | @classmethod 123 | def from_specs(cls, geometry, multiplicity=None, charge=None, basis=None): 124 | mol_spec = MolecularSpec(geometry, multiplicity=multiplicity, charge=charge, basis=basis) 125 | return cls.using_pyscf(mol_spec) 126 | -------------------------------------------------------------------------------- /src/qiskit_alt/electronic_structure.jl: -------------------------------------------------------------------------------- 1 | # ENV["PYCALL_JL_RUNTIME_PYTHON"] = Sys.which("python") or '' 2 | 3 | #using PyCall 4 | using ElectronicStructure: Atom, Geometry, MolecularSpec, 5 | InteractionOperator, MolecularData 6 | 7 | # using ElectronicStructurePySCF: PySCF 8 | 9 | using QuantumOps: FermiSum 10 | 11 | """ 12 | qiskit_geometry_to_Geometry(geometry::Matrix) 13 | 14 | Convert a geometry specification that originated in Python in the qiskit-nature format to 15 | an `ElectronicStructure.Geometry` object. pyjulia will have translated the python input to 16 | a `Matrix`. 17 | 18 | # Example from python: 19 | ```python 20 | In [1]: geometry = [['O', [0., 0., 0.]], 21 | ...: ['H', [0.757, 0.586, 0.]], 22 | ...: ['H', [-0.757, 0.586, 0.]]] 23 | 24 | In [2]: Main.qiskit_geometry_to_Geometry(geometry) 25 | Out[2]: 26 | ``` 27 | """ 28 | function qiskit_geometry_to_Geometry(geometry::Matrix) 29 | (num_atoms, num_components_per_atom) = size(geometry) 30 | if num_components_per_atom != 2 31 | throw(ArgumentError("Expecting two elements to specify geometry of a single Atom. Got ", num_components_per_atom)) 32 | end 33 | atoms = [] 34 | for atom_in in eachrow(geometry) 35 | element_symbol = atom_in[1] 36 | coords = atom_in[2] 37 | atom = Atom(Symbol(element_symbol), (coords...,)) 38 | push!(atoms, atom) 39 | end 40 | return Geometry(atoms...) 41 | end 42 | 43 | function fermionic_hamiltonian(geometry::Geometry, basis) 44 | # Construct the specification of the electronic structure problem. 45 | mol_spec = MolecularSpec(geometry=geometry, basis=basis) 46 | 47 | # Do calculations using `PySCF` and populate a `MolecularData` object with results. 48 | # `mol_pyscf` holds a constant, a rank-two tensor, and a rank-four tensor. 49 | mol_pyscf = MolecularData(PySCF, mol_spec); 50 | iop = InteractionOperator(mol_pyscf); 51 | return FermiSum(iop) 52 | end 53 | 54 | 55 | # Not useful. Just call jordan_wigner(fermi_op) directly 56 | # function qubit_hamiltonian(fermi_op) 57 | # pauli_op = jordan_wigner(fermi_op) 58 | # return pauli_op 59 | # end 60 | 61 | # function qubit_hamiltonian(geometry::Geometry, basis) 62 | # fermi_op = fermionic_hamiltonian(geometry::Geometry, basis) 63 | # pauli_op = jordan_wigner(fermi_op) 64 | # return pauli_op 65 | # end 66 | -------------------------------------------------------------------------------- /src/qiskit_alt/electronic_structure.py: -------------------------------------------------------------------------------- 1 | from .e_struct_python import Atom, Geometry, geometry, atom, MolecularSpec, MolecularData 2 | from .e_struct_julia import fermionic_hamiltonian, jordan_wigner 3 | -------------------------------------------------------------------------------- /src/qiskit_alt/julia_project.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from julia_project import JuliaProject 4 | 5 | qiskit_alt_path = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | 8 | def new_project(calljulia="juliacall"): 9 | """Return a new `JuliaProject`. 10 | 11 | Use this if you want to use both `pyjulia` (`julia` module) and `juliacall` 12 | in a single session. For example, if are already using `pyjulia`, you can 13 | do `new_proj = new_project(calljulia="juliacall")` and then 14 | `new_proj.ensure_init()`. 15 | """ 16 | return JuliaProject( 17 | name="qiskit_alt", 18 | package_path = qiskit_alt_path, 19 | version_spec = "^1.6", # Must be at least 1.6 20 | env_prefix = 'QISKIT_ALT_', # env variables prefixed with this may control JuliaProject 21 | registries = {"QuantumRegistry" : "https://github.com/Qiskit-Extensions/QuantumRegistry"}, 22 | logging_level = logging.INFO, # or logging.WARN, 23 | console_logging=False, 24 | calljulia = calljulia 25 | ) 26 | 27 | 28 | project = new_project() 29 | -------------------------------------------------------------------------------- /src/qiskit_alt/juliacall_util.jl: -------------------------------------------------------------------------------- 1 | function pyconvert_list(::Type{T}, list) where T 2 | vec = Vector{T}(undef, length(list)) 3 | for i in eachindex(list) 4 | vec[i] = PythonCall.pyconvert(T, list[i]) 5 | end 6 | return vec 7 | end 8 | 9 | const pytype_str = PythonCall.pytype(PythonCall.Py("c")) 10 | const pytype_int = PythonCall.pytype(PythonCall.Py(1)) 11 | const pytype_float = PythonCall.pytype(PythonCall.Py(1.0)) 12 | 13 | function pyconvert_list(pT::PythonCall.Py, list) 14 | if isequal(pytype_str, pT) 15 | T = String 16 | elseif isequal(pytype_int, pT) 17 | T = Int 18 | elseif isequal(pytype_float, pT) 19 | T = Float64 20 | else 21 | println("Unsupported") 22 | return nothing # should throw an error 23 | end 24 | return pyconvert_list(T, list) 25 | end 26 | -------------------------------------------------------------------------------- /src/qiskit_alt/pauli_operators.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qiskit.quantum_info import Pauli, SparsePauliOp, PauliList 4 | from .julia_project import project 5 | project.ensure_init() 6 | 7 | Main = project.simple_import("Main") 8 | QuantumOps = project.simple_import("QuantumOps") 9 | QiskitQuantumInfo = project.simple_import("QiskitQuantumInfo") 10 | 11 | if project._calljulia_name == 'juliacall': 12 | Main.include(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'juliacall_util.jl')) 13 | 14 | 15 | def jlPauli(data): 16 | """ 17 | Convert a QiskitQuantumInfo.Pauli to a qiskit.quantum_info.Pauli 18 | """ 19 | if isinstance(data, str): 20 | data = QiskitQuantumInfo.Pauli(data) 21 | return Pauli((data.x, data.z, data.phase)) 22 | 23 | 24 | def jlPauliList(pauli_list): 25 | """ 26 | Convert a QiskitQuantumInfo.PauliList to a qiskit.quantum_info.PauliList 27 | """ 28 | return PauliList.from_symplectic(pauli_list.z, pauli_list.x) 29 | 30 | 31 | def jlSparsePauliOp(sp): 32 | pl = PauliList.from_symplectic(sp.pauli_list.z, sp.pauli_list.x) 33 | return SparsePauliOp(pl, Main.map(Main.float, sp.coeffs)) 34 | 35 | 36 | def PauliSum_to_SparsePauliOp(ps): 37 | """ 38 | Convert a QuantumOps.PauliSum to a qiskit.quantum_info.SparsePauliOp 39 | """ 40 | spop_jl = QiskitQuantumInfo.SparsePauliOp(ps) # Convert to QiskitQuantumInfo.SparsePauliOp 41 | spop = jlSparsePauliOp(spop_jl) # Convert to qisit.quantum_info.SparsePauliOp 42 | return spop 43 | -------------------------------------------------------------------------------- /src/qiskit_alt/sys_image/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" 3 | ElectronicStructure = "f7ec468b-4310-4c77-90aa-f73697106762" 4 | IsApprox = "28f27b66-4bd8-47e7-9110-e2746eb8bed7" 5 | JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" 6 | PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" 7 | QiskitQuantumInfo = "8d55b643-9899-48dd-ae5e-3f792c55f725" 8 | QuantumOps = "d0cc4389-ef80-471f-8a8c-dd305d114ada" 9 | ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89" 10 | Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" 11 | SparseArraysN = "7d983096-c313-11eb-0b95-579bee092dbe" 12 | StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" 13 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 14 | 15 | [compat] 16 | ElectronicStructure = ">= 0.1.4" 17 | JLD2 = ">= 0.4.9" 18 | -------------------------------------------------------------------------------- /src/qiskit_alt/sys_image/compile_exercise_script.jl: -------------------------------------------------------------------------------- 1 | import QuantumOps 2 | import ElectronicStructure 3 | import QiskitQuantumInfo 4 | 5 | include(joinpath(pkgdir(QuantumOps), "test", "runtests.jl")) 6 | include(joinpath(pkgdir(ElectronicStructure), "test", "runtests.jl")) 7 | include(joinpath(pkgdir(QiskitQuantumInfo), "test", "runtests.jl")) 8 | -------------------------------------------------------------------------------- /src/qiskit_alt/sys_image/packages.jl: -------------------------------------------------------------------------------- 1 | [:QuantumOps, :ElectronicStructure, :QiskitQuantumInfo] 2 | -------------------------------------------------------------------------------- /test/basic_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import qiskit_alt 3 | project = qiskit_alt.project 4 | project.ensure_init() # calljulia="pyjulia" 5 | import qiskit_alt.electronic_structure 6 | 7 | Main = project.julia.Main 8 | 9 | def test_always_passes(): 10 | assert True 11 | 12 | 13 | def test_interface_lib(): 14 | assert qiskit_alt.project.julia.__name__ == 'julia' 15 | 16 | 17 | def test_import_QuantumOps(): 18 | project.simple_import("QuantumOps") 19 | assert True 20 | 21 | 22 | def test_import_ElectronicStructure(): 23 | project.simple_import("ElectronicStructure") 24 | assert True 25 | 26 | 27 | def test_import_QiskitQuantumInfo(): 28 | project.simple_import("QiskitQuantumInfo") 29 | assert True 30 | 31 | 32 | @pytest.fixture 33 | def conv_geometry(): 34 | geometry = [['H', [0., 0., 0.]], ['H', [0., 0., 0.7414]]] 35 | # geometry = [['O', [0., 0., 0.]], 36 | # ['H', [0.757, 0.586, 0.]], 37 | # ['H', [-0.757, 0.586, 0.]]] 38 | return qiskit_alt.electronic_structure.Geometry(geometry) 39 | 40 | 41 | def test_Geometry_length(conv_geometry): 42 | assert Main.length(conv_geometry) == 2 43 | 44 | 45 | def test_Geometry_atom(conv_geometry): 46 | atom = Main.getindex(conv_geometry, 1) 47 | assert atom.coords == (0.0, 0.0, 0.0) 48 | 49 | 50 | def test_fermionic_hamiltonian(conv_geometry): 51 | fermi_op = Main.fermionic_hamiltonian(conv_geometry, "sto3g") 52 | assert Main.length(fermi_op) == 25 53 | -------------------------------------------------------------------------------- /test_juliacall/basic_juliacall_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import qiskit_alt 3 | project = qiskit_alt.project 4 | project.ensure_init(calljulia="juliacall") 5 | 6 | def test_always_passes(): 7 | assert True 8 | 9 | def test_interface_lib(): 10 | assert qiskit_alt.project.julia.__name__ == 'juliacall' 11 | 12 | def test_Main(): 13 | Main = qiskit_alt.project.julia.Main 14 | assert Main.sind(90) == 1.0 15 | --------------------------------------------------------------------------------