From e3c54aac4a066ea9da6d44d15aa4f45e92069a91 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 16 Mar 2021 18:01:30 +0100 Subject: [PATCH 01/32] * Importing initial design co-authored by Naoki Kanazawa and Daniel J. Egger. * Previous commit log from the old repo is: commit 0e869b40dcc19685034ff336c7b3170b93be88de (HEAD -> calmod_draft_inst_def, origin/calmod_draft_inst_def) Author: Daniel Egger Date: Tue Feb 16 19:11:38 2021 +0100 * Moved metadata into circuits. * Moved to execute, i.e. no more assemble in BaseCalibrationExperiment. * Removed marginalization in data processing. commit 957acf7587ce84553fb6dc81fa3d4450925239d6 Author: Daniel Egger Date: Tue Feb 16 11:31:29 2021 +0100 * Added TODO. commit 85bd8088c631ce3fa72e6cd5cda9f92b6a8f0400 Author: Daniel Egger Date: Mon Feb 15 13:29:30 2021 +0100 * Cleaned-up the CR calibration example. * Fixed a bug with data in BaseCalibrationAnalysis. commit b018194b32b841479d2250a139617a98f1c3f209 Author: Daniel Egger Date: Sun Feb 14 16:55:34 2021 +0100 * Added CosineDecayFit * Renamed InstructionsDefinitionV2 to CalibrationsDefinition and removed old inst def. * Removed to dict in ParameterValue. * Implemented stub of Ramsey (currently not functional due to lacking functionality in qiskit). commit 65cd71de88d11509a3e0f6fd6f9fe4873aa00e3d Author: Daniel Egger Date: Thu Feb 11 13:37:23 2021 +0100 * Cleaned up spectroscopy. * Added default_analysis method to BaseCalibrationExperiment for convenience. * Added LorentzFit. * Added qubit frequencies from the backend to the InstDef. commit c1ab4e4b8beaf1a4e6ab04d39a2f6860fbeffb98 Author: Daniel Egger Date: Tue Feb 9 19:37:19 2021 +0100 * Added RoughDrag calibration. * Fixed issues with multi-series plotting. * Added function to get the minimum of a cos. * Improved docstring quality here and there. commit 83910fb37e6a0964d469a06cc95332467ee51dd2 Author: Daniel Egger Date: Tue Feb 9 12:43:48 2021 +0100 * Moved current calibrations to InstDefV2 methodology. * Fixed BaseJob -> JobV1 issue in analysis.py. commit 9c80a9d88a68b23f40b1ca79c02230a3288a3f94 Author: Daniel Egger Date: Fri Feb 5 19:05:26 2021 +0100 * Draft of V2 inst def. commit 3a472e13d93306e418bdeb2d0c6870a895372a82 (naoki/calmod_draft, calmod_draft) Merge: 7881495 5b4eaa9 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Mon Dec 14 17:29:37 2020 +0100 Merge pull request #17 from eggerdj/calmod_draft_update_on_analysis Update parameters commit 5b4eaa98e29bda952e59ff37554ee2b82c231fd9 (origin/calmod_draft_update_on_analysis) Author: Daniel Egger Date: Mon Dec 14 16:38:26 2020 +0100 * Added updated pulses to the RoughCRAmplitude cal. (base) daniel@daniel-OMEN:qiskit-ignis$ git log -n 100 commit 0e869b40dcc19685034ff336c7b3170b93be88de (HEAD -> calmod_draft_inst_def, origin/calmod_draft_inst_def) Author: Daniel Egger Date: Tue Feb 16 19:11:38 2021 +0100 * Moved metadata into circuits. * Moved to execute, i.e. no more assemble in BaseCalibrationExperiment. * Removed marginalization in data processing. commit 957acf7587ce84553fb6dc81fa3d4450925239d6 Author: Daniel Egger Date: Tue Feb 16 11:31:29 2021 +0100 * Added TODO. commit 85bd8088c631ce3fa72e6cd5cda9f92b6a8f0400 Author: Daniel Egger Date: Mon Feb 15 13:29:30 2021 +0100 * Cleaned-up the CR calibration example. * Fixed a bug with data in BaseCalibrationAnalysis. commit b018194b32b841479d2250a139617a98f1c3f209 Author: Daniel Egger Date: Sun Feb 14 16:55:34 2021 +0100 * Added CosineDecayFit * Renamed InstructionsDefinitionV2 to CalibrationsDefinition and removed old inst def. * Removed to dict in ParameterValue. * Implemented stub of Ramsey (currently not functional due to lacking functionality in qiskit). commit 65cd71de88d11509a3e0f6fd6f9fe4873aa00e3d Author: Daniel Egger Date: Thu Feb 11 13:37:23 2021 +0100 * Cleaned up spectroscopy. * Added default_analysis method to BaseCalibrationExperiment for convenience. * Added LorentzFit. * Added qubit frequencies from the backend to the InstDef. commit c1ab4e4b8beaf1a4e6ab04d39a2f6860fbeffb98 Author: Daniel Egger Date: Tue Feb 9 19:37:19 2021 +0100 * Added RoughDrag calibration. * Fixed issues with multi-series plotting. * Added function to get the minimum of a cos. * Improved docstring quality here and there. commit 83910fb37e6a0964d469a06cc95332467ee51dd2 Author: Daniel Egger Date: Tue Feb 9 12:43:48 2021 +0100 * Moved current calibrations to InstDefV2 methodology. * Fixed BaseJob -> JobV1 issue in analysis.py. commit 9c80a9d88a68b23f40b1ca79c02230a3288a3f94 Author: Daniel Egger Date: Fri Feb 5 19:05:26 2021 +0100 * Draft of V2 inst def. commit 3a472e13d93306e418bdeb2d0c6870a895372a82 (naoki/calmod_draft, calmod_draft) Merge: 7881495 5b4eaa9 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Mon Dec 14 17:29:37 2020 +0100 Merge pull request #17 from eggerdj/calmod_draft_update_on_analysis Update parameters commit 5b4eaa98e29bda952e59ff37554ee2b82c231fd9 (origin/calmod_draft_update_on_analysis) Author: Daniel Egger Date: Mon Dec 14 16:38:26 2020 +0100 * Added updated pulses to the RoughCRAmplitude cal. commit d0f56a54134551bbdf86b49bf08ec95e0b8ad533 Author: Daniel Egger Date: Thu Dec 10 14:42:50 2020 +0100 * Improved docstrings. commit 6983ce0412d42b1ca5d0ffc8568f5ae42337a5da Author: Daniel Egger Date: Thu Dec 10 14:37:38 2020 +0100 * Added a mechanisme to update several pulses at once in RoughAmplitude. commit eb10052291e09d8f6b9fdb3f1dbbcd308f3dc4ec Author: Daniel Egger Date: Thu Dec 10 14:06:51 2020 +0100 * Moved some of the fit retrieving logic to fit_utils.py. * Did a bit of refactoring on RoughSpectroscopy. commit 230c4afa42c34b1892e2b68acfc4c6a1cf36dd91 Author: Daniel Egger Date: Wed Dec 9 15:03:11 2020 +0100 * Added init to BaseCalibrationExperiment and moved some variables of the child classes to the parent. commit 3852844a861176237aa8b67998ea9b27a31c5d43 Author: Daniel Egger Date: Wed Dec 9 12:07:02 2020 +0100 * Added instruction type Play to filtering in cross_resonance.py. commit 9ef5629b6f04bc214ba6d7fb081244f265a27d64 Author: Daniel Egger Date: Wed Dec 9 11:35:19 2020 +0100 * Added more docstrings in cross_resonance.py. commit ea8d80d62dbcf590ebea10cd40f0a5ec23bce12c Author: Daniel Egger Date: Wed Dec 9 11:27:57 2020 +0100 * Removed xp from cr cal. commit b3aa763f25d4c3b70295f08840726373f727e1a5 Author: Daniel Egger Date: Wed Dec 9 11:11:42 2020 +0100 * Used get_channel in RoughAmplitude. commit 1ee4ffca0f35359aa322d3cdd92bd7d39f8a1a7d Author: Daniel Egger Date: Wed Dec 9 11:07:32 2020 +0100 * Moved the split parameter name method to PulseParameterTable. commit 7242ce6c8779c831b5d164c934a082e45d913c1f Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed Dec 9 10:56:41 2020 +0100 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> commit 68dbaa636c6a2c1b508135f99681cd5cc4f3d5aa Author: Daniel Egger Date: Tue Dec 8 16:35:53 2020 +0100 * Added update_calibrations function to update the parameters in the table. * Refactored code to default to global tag it a given scope is not found in the pulse param table. * Implemented method to get the fraction of a period for a fit. commit 788149515015e6623d4cc886ccfff3c3e99445e9 Merge: 2765632 adedfa9 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Mon Dec 7 20:55:33 2020 +0900 Merge pull request #16 from eggerdj/calmod_draft_2q Calmod draft 2q commit adedfa9bda5295da6c180b51bfa70aac4c4d1dd9 Author: Daniel Egger Date: Mon Dec 7 12:52:53 2020 +0100 * Refactored get_channel_name to get_channel. commit b708d80f99bc1b63b14a5a7e63502aa757926e3e Author: Daniel Egger Date: Mon Dec 7 12:16:48 2020 +0100 * Fixed bug in get_channel_name. commit 3a3fec51b5c9b689dd7525404b744b60aa2e65be Author: Daniel Egger Date: Mon Dec 7 12:11:19 2020 +0100 * Added ch_type input argument to get_channel_name. commit 3fcb2dd21284ffcb706ed8fd5fb54dc9740f472b Author: Daniel Egger Date: Mon Dec 7 12:00:33 2020 +0100 * Improved type hints. * removed pulse_names from RoughCRAmplitude. commit 9ef6c31fed1c5014778863856cdefdf18bbb5e95 Author: Daniel Egger Date: Mon Dec 7 10:00:00 2020 +0100 * Renamed pulse names and improved its docstring. commit c4de9a026c17073c8e313410ed276c38bd8bb163 Author: Daniel Egger Date: Mon Dec 7 09:29:49 2020 +0100 * Improved type hints. * Renamed gate_name in CR cal and made it optional. commit 61cfc99797cfeb2af61e97ea4b04d6cc3c2ad613 Author: Daniel Egger Date: Thu Dec 3 10:20:49 2020 +0100 * Style. commit 5954113f529469c01b54b12f9a1e6959951cb526 Author: Daniel Egger Date: Thu Dec 3 10:02:30 2020 +0100 * Fixed bug due to edge condition in cal_metadata.py. commit c9f7e55ffac8ac00343c6974da2f383e1311643a Author: Daniel Egger Date: Wed Dec 2 13:55:41 2020 +0100 * Refactored generators. commit dda098e64dc03c6fea7d5ed4f0153667ddd57a50 Merge: 996563a 2765632 Author: Daniel Egger Date: Tue Dec 1 19:45:11 2020 +0100 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_draft_2q commit 2765632f56ed210d31feb9a61dc7e0a926188d6b Merge: e19d828 218aab2 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Dec 1 17:24:45 2020 +0900 Merge pull request #14 from eggerdj/calmod_draft_rough_amp Refactor Rough Amp to use inst_def. commit 218aab2dbac175dec236deb9bdc1bbb547767533 Author: Daniel Egger Date: Mon Nov 30 18:11:41 2020 +0100 * Replaced calibration __init__ with __post_init__. commit 996563a659af2d785dd7861ec71154afccb3ee9f Author: Daniel Egger Date: Mon Nov 30 18:06:42 2020 +0100 * First draft of CR example with new framework. commit f3c554d8fc838c0637e202751d33fc72c6e80c29 Author: Daniel Egger Date: Mon Nov 30 13:10:46 2020 +0100 * Added init to CalMetadata. * Removed DC and used positive part of FFT for Cosin initial guess. * Fixed workflow naming bugs. * removed initial_layout in generation as circuit takes this into account. * commit 42d4d6799394bc99ca61eeba7a0b90d8651cd1ef Merge: 82b91bc e19d828 Author: Daniel Egger Date: Sat Nov 28 18:26:15 2020 +0100 Merge branch 'calmod_draft' into calmod_draft_rough_amp commit e19d8284970527bb84d2c2328ee8b3ecd853403b Merge: c08fedb 65e5d76 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Sat Nov 28 01:12:38 2020 +0900 Merge pull request #15 from nkanazawa1989/calmod_schedule_dsl Schedule DSL and AST commit 82b91bc83d79e6e4073057914b602f7e8cc151d8 Author: Daniel Egger Date: Fri Nov 27 15:17:09 2020 +0100 * Updated signature of RoughamplitudeCal. commit 65e5d7614bc59cf51b7bae136872d858d9ba0114 (naoki/calmod_schedule_dsl) Author: knzwnao Date: Thu Nov 26 21:02:10 2020 +0900 add global scope and shape to table commit 755dd98aa57ad324d311333354c9324f16b15ca8 Author: knzwnao Date: Thu Nov 26 04:34:02 2020 +0900 update data structure, pulse shape replacement commit ac304b25d414ab7a0ad431cbe21b27b1ec8f6664 Merge: bd23158 8bbda2d Author: knzwnao Date: Wed Nov 25 01:30:26 2020 +0900 Merge branch 'calmod_schedule_dsl' of github.com:nkanazawa1989/qiskit-ignis into calmod_schedule_dsl commit bd2315842943885ab85231336bc1f1ae63c2e399 Author: knzwnao Date: Wed Nov 25 01:30:13 2020 +0900 review comments commit 8bbda2d736d0bc0c6060a6f1e21d8ef6ead44e92 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 25 01:19:36 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit d9172e0ae9dd9e4238e381b63d6cf25bc097ca03 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 25 01:18:35 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 42aaa445ca7aef10ebbeb3892d176a94ef1454a9 Merge: cbe09ca 2aac632 Author: knzwnao Date: Wed Nov 25 00:59:53 2020 +0900 Merge branch 'calmod_schedule_dsl' of github.com:nkanazawa1989/qiskit-ignis into calmod_schedule_dsl commit 2aac632193c25d32696c7dfd97d22d9c787c8528 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 24 21:48:59 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit cbe09ca790d10a9ac9b32d152aee1ad8240221e0 Author: knzwnao Date: Tue Nov 24 04:21:58 2020 +0900 fix parameter duplication bug commit 3f330b6f8cc74321679a409e8798f5f659978e05 Author: knzwnao Date: Tue Nov 24 04:02:43 2020 +0900 add interface with database commit 0b2f813627d02f0e94c7b7e7aa9ff570cfa03090 Author: knzwnao Date: Mon Nov 23 17:53:20 2020 +0900 update import and type hints commit b77d776f4492c7a598d12f2f1d4a397c0f63da14 Author: knzwnao Date: Mon Nov 23 10:29:39 2020 +0900 update constructor commit b62e6a66a94f460e15916a811cf16dcdf88a871e Author: knzwnao Date: Mon Nov 23 10:21:00 2020 +0900 add temp interface with database commit d96e37500466f01fef8bdc53cb3cf5d6561f6549 Author: knzwnao Date: Sun Nov 22 17:07:22 2020 +0900 add cal DSL compiler commit 5c725a7fb2c4e97ebb15438908dd872c55312c9a Author: Daniel Egger Date: Wed Nov 18 23:09:34 2020 +0100 * Starting to refactor Rough Amp to use inst_def. commit c08fedb122fa3cc1f04f5b1518ccd82f83cf199c Merge: fbd09eb f3d43d3 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Thu Nov 19 00:22:42 2020 +0900 Merge pull request #13 from nkanazawa1989/calmod_fitter_update General analysis base class with dataframe commit f3d43d3d16771a831e708fc0a03f52577b4a3252 (naoki/calmod_fitter_update) Author: knzwnao Date: Wed Nov 18 20:25:42 2020 +0900 Daniel's feedback commit 7ed302f0a8479f4cca04c0de7d2c7f10f847f82d Author: knzwnao Date: Wed Nov 18 17:39:50 2020 +0900 add error commit 0161fc41c9da7f30089eb68e11781c1fc8ade7a6 Author: knzwnao Date: Wed Nov 18 15:45:59 2020 +0900 fix bugs and add new mock commit 4467a78c5d12955ae17547f3347768603c53b690 Author: knzwnao Date: Wed Nov 18 14:39:02 2020 +0900 add generic base analysis class * generic base class * add new mock to test analysis * update data processing steps commit fbd09eba4914c9b944713ebb333ad887108885d0 Merge: 3c258ca 6d67f54 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 17 23:52:24 2020 +0900 Merge pull request #12 from eggerdj/calmod_draft_schedule_db First draft of the class to manage calibrations as circuits commit 6d67f5488ca5bdddc2073fc3ae43c471248c3b79 (origin/calmod_draft_schedule_db) Author: Daniel Egger Date: Tue Nov 17 15:35:38 2020 +0100 * Added flag for u channels in create_z_instruction. commit e48934e8314f6516d90af312c8a4f78703c098ba Author: Daniel Egger Date: Tue Nov 17 12:56:15 2020 +0100 * Added z instruction creation method. commit 6ee4e9fc669288cd9022034c9638b207ef6c1031 Author: Daniel Egger Date: Tue Nov 17 12:37:33 2020 +0100 * Added pulse_parameter_table property. commit 5d3a1a5766a51463b7696a02609ea655c2f3da12 Author: Daniel Egger Date: Tue Nov 17 12:34:16 2020 +0100 * made get_composite private. commit 3bd5c5e4c82882b85e4eda1d1faf1b76e4eea7d2 Author: Daniel Egger Date: Tue Nov 17 12:28:49 2020 +0100 * Added from_backend method. * replaced _get_qubits with _channel_map. commit c55138b1ab7749195511adb3ba36ef5f8b771ab9 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Tue Nov 17 09:29:20 2020 +0100 Apply suggestions from code review Co-authored-by: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> commit 5101740cacd17c9953236b0046e113142e06550a Author: Daniel Egger Date: Fri Nov 13 17:14:27 2020 +0100 * schedules are now deep copied when queried from the instructions so as not to permenantly bind the parameters. commit 43a010fda99ae80e72c0c5e5d798bbf4a82fa041 Author: Daniel Egger Date: Fri Nov 13 15:34:57 2020 +0100 * Added recursive nature to the instructions table. commit 880f258c55949a349ffbf606b414f5cbf2980378 Author: Daniel Egger Date: Fri Nov 13 14:58:20 2020 +0100 * Refactored to schedules. commit 9313578f73244bcb40833588e49fb024e58417e7 Author: Daniel Egger Date: Fri Nov 13 13:37:41 2020 +0100 * Added backend to init and channel mapping instance. * Added handling of control channels. This now raises some issues to deal with. commit 480b17c3ba0ddf2a1e243f5a274bb96a19586fe8 Author: Daniel Egger Date: Thu Nov 12 21:25:52 2020 +0100 * Added integration with PulseTable. commit 238d9fb4e21205603de2f4273f293801cf120612 Author: Daniel Egger Date: Thu Nov 12 19:46:14 2020 +0100 * Bug fixing. commit 2cfc9ab3a2f30ad42c1dcf086c9f4a2b8161316a Author: Daniel Egger Date: Thu Nov 12 17:09:25 2020 +0100 * Fixed bugs with tubles as keys. commit febe29925a8293d117bf1dc4644f18e30204ecc5 Author: Daniel Egger Date: Thu Nov 12 16:49:02 2020 +0100 * First draft of the class to manage calibrations as circuits. commit 3c258cabaa046e4fc3bae5837a7bf27f680863ca Merge: a567e2f 55b42ed Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Thu Nov 12 20:05:58 2020 +0900 Merge pull request #9 from nkanazawa1989/calmod_db2 Database part2 commit 55b42ed647d8519b7e03c5b4fe0286a004ba1ff0 (naoki/calmod_db2) Author: knzwnao Date: Wed Nov 11 20:34:34 2020 +0900 add channel qubit mapping commit f5b62784e2d031c0b1ce28872560ff8f1b8548ed Merge: a47b124 a567e2f Author: knzwnao Date: Wed Nov 11 18:37:29 2020 +0900 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_db2 commit a567e2fca8699c2a002b179aa371c087fb5352db Merge: 4c5774a 20be4c2 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 11 18:33:13 2020 +0900 Merge pull request #11 from nkanazawa1989/calmod_fake_tables Add 2q pulse parameters to table commit 20be4c2304370dfc0fed3551aff62761d9444ed1 (naoki/calmod_fake_tables) Author: knzwnao Date: Wed Nov 11 18:32:41 2020 +0900 update rotary pulse qubit mapping commit dc6af60dafd52aa67043fe64853efc7f15736108 Author: knzwnao Date: Wed Nov 11 18:28:03 2020 +0900 update channel index commit a47b124e88bf6f7b955299e1e90d11f285034dff Author: knzwnao Date: Wed Nov 11 18:26:28 2020 +0900 add channel mapping commit 5cb4bde1f0add1456bc1a54482a33eca8a151869 Author: knzwnao Date: Wed Nov 11 18:19:48 2020 +0900 add opposite cx drive commit a32666300cdfd7d5e7993c45e3c3cd431224828f Merge: 4fc0c8e 4c5774a Author: knzwnao Date: Wed Nov 11 18:00:52 2020 +0900 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_fake_tables commit 4c5774aea66aa4b0272dbbbbc79f8805352a3658 Merge: 3c92b5f 37ce607 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 11 17:56:46 2020 +0900 Merge pull request #8 from nkanazawa1989/calmod_db1 Database part1 commit e1e2e8777b5e496c4f0fc7c58bb61b36713a27c4 Author: knzwnao Date: Wed Nov 11 17:38:17 2020 +0900 readd qubits commit 367c35b88ea63f82f4824de0aaa411ebc9f21abf Author: knzwnao Date: Wed Nov 11 16:54:32 2020 +0900 fix bugs commit e82c99d7d6f5b8b590c685a7153b4f054c752187 Author: knzwnao Date: Wed Nov 11 15:49:17 2020 +0900 remove qubit from pulse table and allow unbound parameter commit 15ca53e20e79c7e8c4ed4d8f23e65e9cc759ff81 Author: knzwnao Date: Wed Nov 11 15:04:57 2020 +0900 remove qubit from parameter hash generation commit c0d9b772aaa8ef866bb594be12821cb4ece05038 Author: knzwnao Date: Wed Nov 11 13:03:32 2020 +0900 fix bug commit 4fc0c8e364cf11b618d09f1b9ea1aef4d2c835ea Author: knzwnao Date: Wed Nov 11 11:49:50 2020 +0900 update mock commit 37ce607f7ad0786732bb21e45da0e9bd19843402 (naoki/calmod_db1) Author: knzwnao Date: Wed Nov 11 10:36:55 2020 +0900 comments from Daniel commit 06c340c05524cd2e2c992cb54b445b3ee0c05e1b Author: knzwnao Date: Wed Nov 11 10:31:24 2020 +0900 comments from Daniel commit 78c43e5d43be026d47b5e8302a93331208e89e68 Author: knzwnao Date: Tue Nov 10 23:37:58 2020 +0900 update mock commit 2dde240a4adace1a813dc5ca6cab5fa8e0692e50 Author: knzwnao Date: Tue Nov 10 23:04:01 2020 +0900 add interface commit 42a963e2441107e3cfd1cea7611eb5090ce2912b Author: knzwnao Date: Tue Nov 10 23:02:33 2020 +0900 code cleanup commit a35c74d6b1ec0bb2f5bb10e06085d2e88e6f80ed Author: knzwnao Date: Tue Nov 10 22:58:40 2020 +0900 add database components commit 3c92b5fe448ad85518f223e22b15b39b73282ce2 Merge: 46568cc ed5bedd Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 10 02:44:32 2020 +0900 Merge pull request #5 from eggerdj/calmod_draft Workflow and general refactoring commit ed5bedd84fd258987a48dcea658cad3cda5b2915 (origin/calmod_draft) Author: Daniel Egger Date: Mon Nov 9 16:30:40 2020 +0100 * Moved docstring. commit 9b327dabd44b937c2fb4352503948605a5811e97 Author: Daniel Egger Date: Mon Nov 9 16:26:16 2020 +0100 * Moved docstring and removed unused import. commit 0c11298433a40c0c27ec54ebd11d5e48d7c5768d Author: Daniel Egger Date: Mon Nov 9 16:22:19 2020 +0100 * Removed unnecessary import in cal_table.py (base) daniel@daniel-OMEN:qiskit-ignis$ git log -n 500 commit 0e869b40dcc19685034ff336c7b3170b93be88de (HEAD -> calmod_draft_inst_def, origin/calmod_draft_inst_def) Author: Daniel Egger Date: Tue Feb 16 19:11:38 2021 +0100 * Moved metadata into circuits. * Moved to execute, i.e. no more assemble in BaseCalibrationExperiment. * Removed marginalization in data processing. commit 957acf7587ce84553fb6dc81fa3d4450925239d6 Author: Daniel Egger Date: Tue Feb 16 11:31:29 2021 +0100 * Added TODO. commit 85bd8088c631ce3fa72e6cd5cda9f92b6a8f0400 Author: Daniel Egger Date: Mon Feb 15 13:29:30 2021 +0100 * Cleaned-up the CR calibration example. * Fixed a bug with data in BaseCalibrationAnalysis. commit b018194b32b841479d2250a139617a98f1c3f209 Author: Daniel Egger Date: Sun Feb 14 16:55:34 2021 +0100 * Added CosineDecayFit * Renamed InstructionsDefinitionV2 to CalibrationsDefinition and removed old inst def. * Removed to dict in ParameterValue. * Implemented stub of Ramsey (currently not functional due to lacking functionality in qiskit). commit 65cd71de88d11509a3e0f6fd6f9fe4873aa00e3d Author: Daniel Egger Date: Thu Feb 11 13:37:23 2021 +0100 * Cleaned up spectroscopy. * Added default_analysis method to BaseCalibrationExperiment for convenience. * Added LorentzFit. * Added qubit frequencies from the backend to the InstDef. commit c1ab4e4b8beaf1a4e6ab04d39a2f6860fbeffb98 Author: Daniel Egger Date: Tue Feb 9 19:37:19 2021 +0100 * Added RoughDrag calibration. * Fixed issues with multi-series plotting. * Added function to get the minimum of a cos. * Improved docstring quality here and there. commit 83910fb37e6a0964d469a06cc95332467ee51dd2 Author: Daniel Egger Date: Tue Feb 9 12:43:48 2021 +0100 * Moved current calibrations to InstDefV2 methodology. * Fixed BaseJob -> JobV1 issue in analysis.py. commit 9c80a9d88a68b23f40b1ca79c02230a3288a3f94 Author: Daniel Egger Date: Fri Feb 5 19:05:26 2021 +0100 * Draft of V2 inst def. commit 3a472e13d93306e418bdeb2d0c6870a895372a82 (naoki/calmod_draft, calmod_draft) Merge: 7881495 5b4eaa9 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Mon Dec 14 17:29:37 2020 +0100 Merge pull request #17 from eggerdj/calmod_draft_update_on_analysis Update parameters commit 5b4eaa98e29bda952e59ff37554ee2b82c231fd9 (origin/calmod_draft_update_on_analysis) Author: Daniel Egger Date: Mon Dec 14 16:38:26 2020 +0100 * Added updated pulses to the RoughCRAmplitude cal. commit d0f56a54134551bbdf86b49bf08ec95e0b8ad533 Author: Daniel Egger Date: Thu Dec 10 14:42:50 2020 +0100 * Improved docstrings. commit 6983ce0412d42b1ca5d0ffc8568f5ae42337a5da Author: Daniel Egger Date: Thu Dec 10 14:37:38 2020 +0100 * Added a mechanisme to update several pulses at once in RoughAmplitude. commit eb10052291e09d8f6b9fdb3f1dbbcd308f3dc4ec Author: Daniel Egger Date: Thu Dec 10 14:06:51 2020 +0100 * Moved some of the fit retrieving logic to fit_utils.py. * Did a bit of refactoring on RoughSpectroscopy. commit 230c4afa42c34b1892e2b68acfc4c6a1cf36dd91 Author: Daniel Egger Date: Wed Dec 9 15:03:11 2020 +0100 * Added init to BaseCalibrationExperiment and moved some variables of the child classes to the parent. commit 3852844a861176237aa8b67998ea9b27a31c5d43 Author: Daniel Egger Date: Wed Dec 9 12:07:02 2020 +0100 * Added instruction type Play to filtering in cross_resonance.py. commit 9ef5629b6f04bc214ba6d7fb081244f265a27d64 Author: Daniel Egger Date: Wed Dec 9 11:35:19 2020 +0100 * Added more docstrings in cross_resonance.py. commit ea8d80d62dbcf590ebea10cd40f0a5ec23bce12c Author: Daniel Egger Date: Wed Dec 9 11:27:57 2020 +0100 * Removed xp from cr cal. commit b3aa763f25d4c3b70295f08840726373f727e1a5 Author: Daniel Egger Date: Wed Dec 9 11:11:42 2020 +0100 * Used get_channel in RoughAmplitude. commit 1ee4ffca0f35359aa322d3cdd92bd7d39f8a1a7d Author: Daniel Egger Date: Wed Dec 9 11:07:32 2020 +0100 * Moved the split parameter name method to PulseParameterTable. commit 7242ce6c8779c831b5d164c934a082e45d913c1f Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed Dec 9 10:56:41 2020 +0100 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> commit 68dbaa636c6a2c1b508135f99681cd5cc4f3d5aa Author: Daniel Egger Date: Tue Dec 8 16:35:53 2020 +0100 * Added update_calibrations function to update the parameters in the table. * Refactored code to default to global tag it a given scope is not found in the pulse param table. * Implemented method to get the fraction of a period for a fit. commit 788149515015e6623d4cc886ccfff3c3e99445e9 Merge: 2765632 adedfa9 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Mon Dec 7 20:55:33 2020 +0900 Merge pull request #16 from eggerdj/calmod_draft_2q Calmod draft 2q commit adedfa9bda5295da6c180b51bfa70aac4c4d1dd9 Author: Daniel Egger Date: Mon Dec 7 12:52:53 2020 +0100 * Refactored get_channel_name to get_channel. commit b708d80f99bc1b63b14a5a7e63502aa757926e3e Author: Daniel Egger Date: Mon Dec 7 12:16:48 2020 +0100 * Fixed bug in get_channel_name. commit 3a3fec51b5c9b689dd7525404b744b60aa2e65be Author: Daniel Egger Date: Mon Dec 7 12:11:19 2020 +0100 * Added ch_type input argument to get_channel_name. commit 3fcb2dd21284ffcb706ed8fd5fb54dc9740f472b Author: Daniel Egger Date: Mon Dec 7 12:00:33 2020 +0100 * Improved type hints. * removed pulse_names from RoughCRAmplitude. commit 9ef6c31fed1c5014778863856cdefdf18bbb5e95 Author: Daniel Egger Date: Mon Dec 7 10:00:00 2020 +0100 * Renamed pulse names and improved its docstring. commit c4de9a026c17073c8e313410ed276c38bd8bb163 Author: Daniel Egger Date: Mon Dec 7 09:29:49 2020 +0100 * Improved type hints. * Renamed gate_name in CR cal and made it optional. commit 61cfc99797cfeb2af61e97ea4b04d6cc3c2ad613 Author: Daniel Egger Date: Thu Dec 3 10:20:49 2020 +0100 * Style. commit 5954113f529469c01b54b12f9a1e6959951cb526 Author: Daniel Egger Date: Thu Dec 3 10:02:30 2020 +0100 * Fixed bug due to edge condition in cal_metadata.py. commit c9f7e55ffac8ac00343c6974da2f383e1311643a Author: Daniel Egger Date: Wed Dec 2 13:55:41 2020 +0100 * Refactored generators. commit dda098e64dc03c6fea7d5ed4f0153667ddd57a50 Merge: 996563a 2765632 Author: Daniel Egger Date: Tue Dec 1 19:45:11 2020 +0100 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_draft_2q commit 2765632f56ed210d31feb9a61dc7e0a926188d6b Merge: e19d828 218aab2 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Dec 1 17:24:45 2020 +0900 Merge pull request #14 from eggerdj/calmod_draft_rough_amp Refactor Rough Amp to use inst_def. commit 218aab2dbac175dec236deb9bdc1bbb547767533 Author: Daniel Egger Date: Mon Nov 30 18:11:41 2020 +0100 * Replaced calibration __init__ with __post_init__. commit 996563a659af2d785dd7861ec71154afccb3ee9f Author: Daniel Egger Date: Mon Nov 30 18:06:42 2020 +0100 * First draft of CR example with new framework. commit f3c554d8fc838c0637e202751d33fc72c6e80c29 Author: Daniel Egger Date: Mon Nov 30 13:10:46 2020 +0100 * Added init to CalMetadata. * Removed DC and used positive part of FFT for Cosin initial guess. * Fixed workflow naming bugs. * removed initial_layout in generation as circuit takes this into account. * commit 42d4d6799394bc99ca61eeba7a0b90d8651cd1ef Merge: 82b91bc e19d828 Author: Daniel Egger Date: Sat Nov 28 18:26:15 2020 +0100 Merge branch 'calmod_draft' into calmod_draft_rough_amp commit e19d8284970527bb84d2c2328ee8b3ecd853403b Merge: c08fedb 65e5d76 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Sat Nov 28 01:12:38 2020 +0900 Merge pull request #15 from nkanazawa1989/calmod_schedule_dsl Schedule DSL and AST commit 82b91bc83d79e6e4073057914b602f7e8cc151d8 Author: Daniel Egger Date: Fri Nov 27 15:17:09 2020 +0100 * Updated signature of RoughamplitudeCal. commit 65e5d7614bc59cf51b7bae136872d858d9ba0114 (naoki/calmod_schedule_dsl) Author: knzwnao Date: Thu Nov 26 21:02:10 2020 +0900 add global scope and shape to table commit 755dd98aa57ad324d311333354c9324f16b15ca8 Author: knzwnao Date: Thu Nov 26 04:34:02 2020 +0900 update data structure, pulse shape replacement commit ac304b25d414ab7a0ad431cbe21b27b1ec8f6664 Merge: bd23158 8bbda2d Author: knzwnao Date: Wed Nov 25 01:30:26 2020 +0900 Merge branch 'calmod_schedule_dsl' of github.com:nkanazawa1989/qiskit-ignis into calmod_schedule_dsl commit bd2315842943885ab85231336bc1f1ae63c2e399 Author: knzwnao Date: Wed Nov 25 01:30:13 2020 +0900 review comments commit 8bbda2d736d0bc0c6060a6f1e21d8ef6ead44e92 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 25 01:19:36 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit d9172e0ae9dd9e4238e381b63d6cf25bc097ca03 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 25 01:18:35 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 42aaa445ca7aef10ebbeb3892d176a94ef1454a9 Merge: cbe09ca 2aac632 Author: knzwnao Date: Wed Nov 25 00:59:53 2020 +0900 Merge branch 'calmod_schedule_dsl' of github.com:nkanazawa1989/qiskit-ignis into calmod_schedule_dsl commit 2aac632193c25d32696c7dfd97d22d9c787c8528 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 24 21:48:59 2020 +0900 Update qiskit/ignis/experiments/calibration/instruction_data/compiler.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit cbe09ca790d10a9ac9b32d152aee1ad8240221e0 Author: knzwnao Date: Tue Nov 24 04:21:58 2020 +0900 fix parameter duplication bug commit 3f330b6f8cc74321679a409e8798f5f659978e05 Author: knzwnao Date: Tue Nov 24 04:02:43 2020 +0900 add interface with database commit 0b2f813627d02f0e94c7b7e7aa9ff570cfa03090 Author: knzwnao Date: Mon Nov 23 17:53:20 2020 +0900 update import and type hints commit b77d776f4492c7a598d12f2f1d4a397c0f63da14 Author: knzwnao Date: Mon Nov 23 10:29:39 2020 +0900 update constructor commit b62e6a66a94f460e15916a811cf16dcdf88a871e Author: knzwnao Date: Mon Nov 23 10:21:00 2020 +0900 add temp interface with database commit d96e37500466f01fef8bdc53cb3cf5d6561f6549 Author: knzwnao Date: Sun Nov 22 17:07:22 2020 +0900 add cal DSL compiler commit 5c725a7fb2c4e97ebb15438908dd872c55312c9a Author: Daniel Egger Date: Wed Nov 18 23:09:34 2020 +0100 * Starting to refactor Rough Amp to use inst_def. commit c08fedb122fa3cc1f04f5b1518ccd82f83cf199c Merge: fbd09eb f3d43d3 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Thu Nov 19 00:22:42 2020 +0900 Merge pull request #13 from nkanazawa1989/calmod_fitter_update General analysis base class with dataframe commit f3d43d3d16771a831e708fc0a03f52577b4a3252 (naoki/calmod_fitter_update) Author: knzwnao Date: Wed Nov 18 20:25:42 2020 +0900 Daniel's feedback commit 7ed302f0a8479f4cca04c0de7d2c7f10f847f82d Author: knzwnao Date: Wed Nov 18 17:39:50 2020 +0900 add error commit 0161fc41c9da7f30089eb68e11781c1fc8ade7a6 Author: knzwnao Date: Wed Nov 18 15:45:59 2020 +0900 fix bugs and add new mock commit 4467a78c5d12955ae17547f3347768603c53b690 Author: knzwnao Date: Wed Nov 18 14:39:02 2020 +0900 add generic base analysis class * generic base class * add new mock to test analysis * update data processing steps commit fbd09eba4914c9b944713ebb333ad887108885d0 Merge: 3c258ca 6d67f54 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 17 23:52:24 2020 +0900 Merge pull request #12 from eggerdj/calmod_draft_schedule_db First draft of the class to manage calibrations as circuits commit 6d67f5488ca5bdddc2073fc3ae43c471248c3b79 (origin/calmod_draft_schedule_db) Author: Daniel Egger Date: Tue Nov 17 15:35:38 2020 +0100 * Added flag for u channels in create_z_instruction. commit e48934e8314f6516d90af312c8a4f78703c098ba Author: Daniel Egger Date: Tue Nov 17 12:56:15 2020 +0100 * Added z instruction creation method. commit 6ee4e9fc669288cd9022034c9638b207ef6c1031 Author: Daniel Egger Date: Tue Nov 17 12:37:33 2020 +0100 * Added pulse_parameter_table property. commit 5d3a1a5766a51463b7696a02609ea655c2f3da12 Author: Daniel Egger Date: Tue Nov 17 12:34:16 2020 +0100 * made get_composite private. commit 3bd5c5e4c82882b85e4eda1d1faf1b76e4eea7d2 Author: Daniel Egger Date: Tue Nov 17 12:28:49 2020 +0100 * Added from_backend method. * replaced _get_qubits with _channel_map. commit c55138b1ab7749195511adb3ba36ef5f8b771ab9 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Tue Nov 17 09:29:20 2020 +0100 Apply suggestions from code review Co-authored-by: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> commit 5101740cacd17c9953236b0046e113142e06550a Author: Daniel Egger Date: Fri Nov 13 17:14:27 2020 +0100 * schedules are now deep copied when queried from the instructions so as not to permenantly bind the parameters. commit 43a010fda99ae80e72c0c5e5d798bbf4a82fa041 Author: Daniel Egger Date: Fri Nov 13 15:34:57 2020 +0100 * Added recursive nature to the instructions table. commit 880f258c55949a349ffbf606b414f5cbf2980378 Author: Daniel Egger Date: Fri Nov 13 14:58:20 2020 +0100 * Refactored to schedules. commit 9313578f73244bcb40833588e49fb024e58417e7 Author: Daniel Egger Date: Fri Nov 13 13:37:41 2020 +0100 * Added backend to init and channel mapping instance. * Added handling of control channels. This now raises some issues to deal with. commit 480b17c3ba0ddf2a1e243f5a274bb96a19586fe8 Author: Daniel Egger Date: Thu Nov 12 21:25:52 2020 +0100 * Added integration with PulseTable. commit 238d9fb4e21205603de2f4273f293801cf120612 Author: Daniel Egger Date: Thu Nov 12 19:46:14 2020 +0100 * Bug fixing. commit 2cfc9ab3a2f30ad42c1dcf086c9f4a2b8161316a Author: Daniel Egger Date: Thu Nov 12 17:09:25 2020 +0100 * Fixed bugs with tubles as keys. commit febe29925a8293d117bf1dc4644f18e30204ecc5 Author: Daniel Egger Date: Thu Nov 12 16:49:02 2020 +0100 * First draft of the class to manage calibrations as circuits. commit 3c258cabaa046e4fc3bae5837a7bf27f680863ca Merge: a567e2f 55b42ed Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Thu Nov 12 20:05:58 2020 +0900 Merge pull request #9 from nkanazawa1989/calmod_db2 Database part2 commit 55b42ed647d8519b7e03c5b4fe0286a004ba1ff0 (naoki/calmod_db2) Author: knzwnao Date: Wed Nov 11 20:34:34 2020 +0900 add channel qubit mapping commit f5b62784e2d031c0b1ce28872560ff8f1b8548ed Merge: a47b124 a567e2f Author: knzwnao Date: Wed Nov 11 18:37:29 2020 +0900 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_db2 commit a567e2fca8699c2a002b179aa371c087fb5352db Merge: 4c5774a 20be4c2 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 11 18:33:13 2020 +0900 Merge pull request #11 from nkanazawa1989/calmod_fake_tables Add 2q pulse parameters to table commit 20be4c2304370dfc0fed3551aff62761d9444ed1 (naoki/calmod_fake_tables) Author: knzwnao Date: Wed Nov 11 18:32:41 2020 +0900 update rotary pulse qubit mapping commit dc6af60dafd52aa67043fe64853efc7f15736108 Author: knzwnao Date: Wed Nov 11 18:28:03 2020 +0900 update channel index commit a47b124e88bf6f7b955299e1e90d11f285034dff Author: knzwnao Date: Wed Nov 11 18:26:28 2020 +0900 add channel mapping commit 5cb4bde1f0add1456bc1a54482a33eca8a151869 Author: knzwnao Date: Wed Nov 11 18:19:48 2020 +0900 add opposite cx drive commit a32666300cdfd7d5e7993c45e3c3cd431224828f Merge: 4fc0c8e 4c5774a Author: knzwnao Date: Wed Nov 11 18:00:52 2020 +0900 Merge branch 'calmod_draft' of github.com:nkanazawa1989/qiskit-ignis into calmod_fake_tables commit 4c5774aea66aa4b0272dbbbbc79f8805352a3658 Merge: 3c92b5f 37ce607 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 11 17:56:46 2020 +0900 Merge pull request #8 from nkanazawa1989/calmod_db1 Database part1 commit e1e2e8777b5e496c4f0fc7c58bb61b36713a27c4 Author: knzwnao Date: Wed Nov 11 17:38:17 2020 +0900 readd qubits commit 367c35b88ea63f82f4824de0aaa411ebc9f21abf Author: knzwnao Date: Wed Nov 11 16:54:32 2020 +0900 fix bugs commit e82c99d7d6f5b8b590c685a7153b4f054c752187 Author: knzwnao Date: Wed Nov 11 15:49:17 2020 +0900 remove qubit from pulse table and allow unbound parameter commit 15ca53e20e79c7e8c4ed4d8f23e65e9cc759ff81 Author: knzwnao Date: Wed Nov 11 15:04:57 2020 +0900 remove qubit from parameter hash generation commit c0d9b772aaa8ef866bb594be12821cb4ece05038 Author: knzwnao Date: Wed Nov 11 13:03:32 2020 +0900 fix bug commit 4fc0c8e364cf11b618d09f1b9ea1aef4d2c835ea Author: knzwnao Date: Wed Nov 11 11:49:50 2020 +0900 update mock commit 37ce607f7ad0786732bb21e45da0e9bd19843402 (naoki/calmod_db1) Author: knzwnao Date: Wed Nov 11 10:36:55 2020 +0900 comments from Daniel commit 06c340c05524cd2e2c992cb54b445b3ee0c05e1b Author: knzwnao Date: Wed Nov 11 10:31:24 2020 +0900 comments from Daniel commit 78c43e5d43be026d47b5e8302a93331208e89e68 Author: knzwnao Date: Tue Nov 10 23:37:58 2020 +0900 update mock commit 2dde240a4adace1a813dc5ca6cab5fa8e0692e50 Author: knzwnao Date: Tue Nov 10 23:04:01 2020 +0900 add interface commit 42a963e2441107e3cfd1cea7611eb5090ce2912b Author: knzwnao Date: Tue Nov 10 23:02:33 2020 +0900 code cleanup commit a35c74d6b1ec0bb2f5bb10e06085d2e88e6f80ed Author: knzwnao Date: Tue Nov 10 22:58:40 2020 +0900 add database components commit 3c92b5fe448ad85518f223e22b15b39b73282ce2 Merge: 46568cc ed5bedd Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 10 02:44:32 2020 +0900 Merge pull request #5 from eggerdj/calmod_draft Workflow and general refactoring commit ed5bedd84fd258987a48dcea658cad3cda5b2915 (origin/calmod_draft) Author: Daniel Egger Date: Mon Nov 9 16:30:40 2020 +0100 * Moved docstring. commit 9b327dabd44b937c2fb4352503948605a5811e97 Author: Daniel Egger Date: Mon Nov 9 16:26:16 2020 +0100 * Moved docstring and removed unused import. commit 0c11298433a40c0c27ec54ebd11d5e48d7c5768d Author: Daniel Egger Date: Mon Nov 9 16:22:19 2020 +0100 * Removed unnecessary import in cal_table.py commit beb97177e7192a8665e660da221d4d0cfac43c72 Author: Daniel Egger Date: Mon Nov 9 16:16:55 2020 +0100 * Reverted cal_table.py to multiply parameter with caomplex phase. commit da917b23669c61d6c1a5601c686473b71bec2253 Author: Daniel Egger Date: Mon Nov 9 13:45:00 2020 +0100 * Improved docstring. commit 0503f638f66b9d03a29b87196b69856bd78940db Author: Daniel Egger Date: Mon Nov 9 13:31:08 2020 +0100 * Removed comment. commit e79264a48a05fb9c2bd69cfafc66c528fbba47bc Author: Daniel Egger Date: Fri Nov 6 17:44:30 2020 +0100 * General refactoring and improving transparancy: - Moved workflow into Analysis. This simplified BaseCalibrationExperiment quite a bit. - Improved docstrings. - Made template circuits non-private as the user may which to see them. - Refactored imports a bit. - Parameters of Generator are now a property so that we can see them. - Added test in PulseTable to not do anything to 'amp' when it is a ParameterExpression. commit 46568cce9765f1f0d18139874a149bfae40fafb8 Merge: fbfea77 6dbde2f Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 21:23:16 2020 +0900 Merge pull request #4 from nkanazawa1989/calmod_replace_db Replace old CalibrationTable with ParameterTable and cleanup codebase commit 6dbde2f8a46d313f2c93c4d0d48cf6616dfedea6 (naoki/calmod_replace_db) Author: knzwnao Date: Wed Nov 4 21:19:05 2020 +0900 generator pulse defaults to drag commit 6d60c3b6aebd5b43bd7d5b38a1dbc385489296fc Author: knzwnao Date: Wed Nov 4 21:09:20 2020 +0900 update import commit 43c1930dba4d04e12df475fd09b2085a682b9df9 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 21:05:32 2020 +0900 Update qiskit/ignis/experiments/calibration/generators/single_qubit.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit a78d330e0668820a81f7f6489eac40d997bad722 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 21:05:20 2020 +0900 Update qiskit/ignis/experiments/calibration/generators/single_qubit.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit e12e2e67aa330a36ded4c3c7f6c4b890d235f817 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 21:04:53 2020 +0900 Update qiskit/ignis/experiments/calibration/methods/basic_1q.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 50872ee8b6ac2a680131b2f026dfcf2d99cbff51 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 20:35:16 2020 +0900 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 09c8e14cc0b7310ec43c90fb75fca23a5d84f9d5 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 19:45:21 2020 +0900 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 802d2876705dbd1e2a296d7e5039ffcd5137ebd3 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 19:45:11 2020 +0900 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit e3fe97e1e6680a34062e0f21eb1cc280d0d87758 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 19:45:01 2020 +0900 Update qiskit/ignis/experiments/calibration/cal_base_generator.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 5322900d6f6012abd69bbff8a5ac280ee7a40f27 Author: knzwnao Date: Wed Nov 4 19:06:50 2020 +0900 update table name and docstring commit c425383101475c826b6b3f3def89fc2d727ecb86 Author: knzwnao Date: Wed Nov 4 04:57:43 2020 +0900 update series name commit eb08a62e50eb10aa60362f3d05e7164996e37597 Author: knzwnao Date: Wed Nov 4 04:53:33 2020 +0900 fix bug commit baa2d814fadb3ba1b9e6249b8c88b134f9bcd98d Author: knzwnao Date: Wed Nov 4 04:51:01 2020 +0900 update comment commit b4059d9c9375687c182df03e645d53bd57a48e4d Author: knzwnao Date: Wed Nov 4 04:37:25 2020 +0900 replace old db and cleanup codebase commit fbfea77eac1596102905648dc77ecd0b9f4b5fea Merge: 93d1816 722dedb Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Wed Nov 4 02:05:36 2020 +0900 Merge pull request #2 from nkanazawa1989/calmod_db_update update database commit 722dedb804d62b985e31f8fe32394083ae68ed71 (naoki/calmod_db_update) Author: knzwnao Date: Wed Nov 4 01:58:17 2020 +0900 fix docstring commit f1a46668a06d272c943c89eba134cb31ef8e99d3 Author: knzwnao Date: Wed Nov 4 01:55:13 2020 +0900 fix docstring commit d24b26247b2261b4164a7c493fbb0edb6e9fc256 Author: knzwnao Date: Wed Nov 4 01:50:17 2020 +0900 add demo1 commit 0e32a2798c8de2258639ff029ca2ee163c01753a Merge: db65062 3332404 Author: knzwnao Date: Tue Nov 3 23:51:27 2020 +0900 Merge branch 'calmod_db_update' of github.com:nkanazawa1989/qiskit-ignis into calmod_db_update commit 3332404b7c019fd7150a7c865ec9ea4cd70e1d31 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 3 23:14:59 2020 +0900 Update qiskit/ignis/experiments/calibration/cal_table.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> commit 93d181666ef7dd8dcf1af11532ba36f17d95de7f Merge: 246b9b8 fc0bd08 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Tue Nov 3 18:36:32 2020 +0900 Merge pull request #3 from eggerdj/calmod_draft [WIP] Analysis framework commit fc0bd08ad8c0653b72dc414a29159ed288ae6afd Author: Daniel Egger Date: Tue Nov 3 09:55:47 2020 +0100 * Added colormap for plotting. commit 8e5f47c6cc5574cfb6236fc49618383fbc2c3846 Author: Daniel Egger Date: Tue Nov 3 09:35:57 2020 +0100 * Refactored imports. commit 4d26d080f9bb457c5e47e2486dbafde6afc50e61 Author: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Tue Nov 3 09:20:58 2020 +0100 Apply suggestions from code review Co-authored-by: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> commit 32340e906ec7d491c8fdb571a8f790ff8c617d2d Author: Daniel Egger Date: Mon Nov 2 18:59:16 2020 +0100 * Renamed BaseCalibrationAnalysis to Calibration1DAnalysis. * Allowed curve_fit to fail with try catch. * Slightly change xvals and yvals and how they are used in plot. * Refactored how metadata is processed: - Needed to initialize CalibrationMetadata from dict. commit d2e53ee1e5000cc98780b11707b3cb31552b47dd Author: Daniel Egger Date: Mon Nov 2 12:30:14 2020 +0100 * Added metadata class for calibration. * Refactored x and y value extraction. * Refactored SinglePulseGenerator. commit db6506236c60bc7d9c42fe527d7dc11e78111273 Author: knzwnao Date: Mon Nov 2 10:59:16 2020 +0900 remove complex amplitude commit bb32106df21c2d26f5b286470b3935efc90dfbdf Author: knzwnao Date: Mon Nov 2 10:42:41 2020 +0900 update mock commit ff2f45aa46ac2ea9da483073cda9fc2b229c3710 Author: knzwnao Date: Mon Nov 2 09:48:47 2020 +0900 update local parameter database commit 246b9b8a89a8f554c664b2b46ca798596e0272b7 Merge: dc62467 e0bfb45 Author: Naoki Kanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Fri Oct 30 20:05:56 2020 +0900 Merge pull request #1 from eggerdj/calmod_draft * Added alternative example of RabiGenerator. commit e0bfb45713440788aaf2ff71ffd417780d6d1048 Author: Daniel Egger Date: Fri Oct 30 11:57:28 2020 +0100 * Added pulse_envelope. * Fixed channel to which the pulse is applied. commit 88f6f9732006987d6487884396b6ecf16620e60e Author: Daniel Egger Date: Fri Oct 30 11:37:51 2020 +0100 * Refactored code to single qubit. * Added self.tamplate_qcs. commit e98d6da38214d92f3c44a237b8d43a308e095f96 Author: Daniel Egger Date: Fri Oct 30 09:34:04 2020 +0100 * Added name. * Added metadata. * Changed to SetFrequency. commit 2a9ced5b738f169f20450b50d188070cfcfc27a2 Author: Daniel Egger Date: Thu Oct 29 12:12:43 2020 +0100 * changed freq_shift and phase_shift to frequency and phase. commit 31ddc0c1ee42c948f8445d2cc6dc0b688c3f8d2f Author: Daniel Egger Date: Thu Oct 29 12:01:48 2020 +0100 * Refactored RabiGenerator to make it more versatile. commit 86014ff006d36735dbf5a5404618c652a203b8c1 Author: Daniel Egger Date: Wed Oct 28 17:36:47 2020 +0100 * Added alternative example of RabiGenerator. commit dc62467d149be1b4279bfb9e320626b2e108380b Author: knzwnao Date: Tue Oct 20 18:29:00 2020 +0900 remove notebook commit 593b8f6cb4236199dc7449c5a4d5ae799029059d Author: knzwnao Date: Thu Oct 15 03:55:42 2020 +0900 add demo1 commit 1774f117e7eec9346cadd60cd87b6eaa86fd40ff Author: knzwnao Date: Wed Oct 14 19:06:34 2020 +0900 add fitting module commit a3c6f072f7d51134de1f40edfe018a3d62442905 Author: knzwnao Date: Wed Oct 14 14:55:22 2020 +0900 implement data processing routines commit b80213d913d1962bbe0e7d0619c9c6f48cabf762 Author: knzwnao Date: Wed Oct 14 12:04:39 2020 +0900 update model of routines commit 00715c9905e8389c036fd551143d59bebdbcaa37 Author: knzwnao Date: Tue Oct 13 18:18:43 2020 +0900 add workflow commit ba2f949bf7a9726c6f694216081e18398f1c2d35 Author: knzwnao Date: Sat Oct 10 16:14:36 2020 +0900 add calibration builder commit 95707516e545a330d80dfb796871844a30894ca6 Author: knzwnao Date: Wed Sep 30 22:37:41 2020 +0900 update notebook commit 8d242d0f2abe6fd68a3818a5183cfe025a9c1b2b Author: knzwnao Date: Wed Sep 30 22:36:16 2020 +0900 update notebook commit b7cc6e6eb00a73da6815211115e72d8d2a46471c Author: knzwnao Date: Wed Sep 30 22:28:38 2020 +0900 add notebook commit 927262a5b7f0f610c35415a1437ec2eb77326bb5 Author: knzwnao Date: Wed Sep 30 22:23:45 2020 +0900 add rabi example commit 7c9088a55c68b48000d688a7311a1746826f0fd3 Author: knzwnao Date: Wed Sep 30 19:50:14 2020 +0900 wip Co-authored-by: Naoki Kanazawa --- qiskit_experiments/calibration/__init__.py | 15 + .../calibration/analysis/__init__.py | 11 + .../analysis/calibration_analysis.py | 190 +++++++++ .../calibration/analysis/trigonometric.py | 76 ++++ .../calibration/calibration_definitions.py | 362 ++++++++++++++++++ .../calibration/data_processing/__init__.py | 16 + .../calibration/data_processing/base.py | 140 +++++++ .../data_processing/data_processor.py | 162 ++++++++ .../calibration/data_processing/nodes.py | 143 +++++++ qiskit_experiments/calibration/exceptions.py | 28 ++ .../calibration/experiments/__init__.py | 13 + .../experiments/rough_amplitude.py | 29 ++ qiskit_experiments/calibration/metadata.py | 62 +++ .../calibration/parameter_value.py | 36 ++ test/calibration/__init__.py | 0 test/calibration/test_data_processing.py | 58 +++ 16 files changed, 1341 insertions(+) create mode 100644 qiskit_experiments/calibration/__init__.py create mode 100644 qiskit_experiments/calibration/analysis/__init__.py create mode 100644 qiskit_experiments/calibration/analysis/calibration_analysis.py create mode 100644 qiskit_experiments/calibration/analysis/trigonometric.py create mode 100644 qiskit_experiments/calibration/calibration_definitions.py create mode 100644 qiskit_experiments/calibration/data_processing/__init__.py create mode 100644 qiskit_experiments/calibration/data_processing/base.py create mode 100644 qiskit_experiments/calibration/data_processing/data_processor.py create mode 100644 qiskit_experiments/calibration/data_processing/nodes.py create mode 100644 qiskit_experiments/calibration/exceptions.py create mode 100644 qiskit_experiments/calibration/experiments/__init__.py create mode 100644 qiskit_experiments/calibration/experiments/rough_amplitude.py create mode 100644 qiskit_experiments/calibration/metadata.py create mode 100644 qiskit_experiments/calibration/parameter_value.py create mode 100644 test/calibration/__init__.py create mode 100644 test/calibration/test_data_processing.py diff --git a/qiskit_experiments/calibration/__init__.py b/qiskit_experiments/calibration/__init__.py new file mode 100644 index 0000000000..abf2396e56 --- /dev/null +++ b/qiskit_experiments/calibration/__init__.py @@ -0,0 +1,15 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Qiskit calibration root.""" + +from .data_processing import DataProcessor diff --git a/qiskit_experiments/calibration/analysis/__init__.py b/qiskit_experiments/calibration/analysis/__init__.py new file mode 100644 index 0000000000..96c0cf22be --- /dev/null +++ b/qiskit_experiments/calibration/analysis/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py new file mode 100644 index 0000000000..0f9e5ba800 --- /dev/null +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -0,0 +1,190 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Base analysis class for calibrations.""" + +import numpy as np +from abc import abstractmethod +from typing import Iterator, List, Optional, Tuple + +from qiskit_experiments.base_analysis import BaseAnalysis +from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments import ExperimentData + + +class BaseCalibrationAnalysis(BaseAnalysis): + """Abstract base class for all calibration analysis classes.""" + + def __init__(self, experiment_data, + data_processor: Optional[DataProcessor] = None, + **options): + """Run analysis on circuit data. + + Args: + experiment_data (ExperimentData): the experiment data to analyze. + data_processor: Specifies the actions to apply when processing the measured data. + options: kwarg options for analysis function. + + Returns: + tuple: A pair ``(analysis_results, figures)`` where + ``analysis_results`` may be a single or list of + AnalysisResult objects, and ``figures`` may be + None, a single figure, or a list of figures. + """ + + self._data_processor = data_processor + + @property + def data_processor(self) -> DataProcessor: + """Return the data processor.""" + return self._data_processor + + @data_processor.setter + def data_processor(self, data_processor: DataProcessor): + """Set the data processor.""" + self._data_processor = data_processor + + @abstractmethod + def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.ndarray]: + """Create initial guess for fit parameters. + + Args: + xvals: x values to fit. + yvals: y values to fit. + + Yield: + Set of initial guess for parameters. + If multiple guesses are returned fit is performed for all parameter set. + Error is measured by Chi squared value and the best fit result is chosen. + + Note: + This should return values with yield rather than return. + """ + + @abstractmethod + def fit_boundary(self, xvals: np.ndarray, yvals: np.ndarray) -> List[Tuple[float, float]]: + """Returns boundary of parameters to fit. + + Args: + xvals: x values to fit. + yvals: y values to fit. + """ + + @abstractmethod + def fit_function(self, xvals: np.ndarray, *args) -> np.ndarray: + """Fit function. + + Args: + xvals: x values to fit. + """ + + def chi_squared(self, + parameters: np.ndarray, + xvals: np.ndarray, + yvals: np.ndarray): + """Calculate reduced Chi squared value. + + Args: + parameters: Parameters for fit function. + xvals: X values to fit. + yvals: Y values to fit. + """ + fit_yvals = self.fit_function(xvals, *parameters) + + chi_sq = sum((fit_yvals - yvals) ** 2) + dof = len(xvals) - len(parameters) + + return chi_sq / dof + + def _run_analysis(self, experiment_data: ExperimentData, **kwargs) -> any: + """ + TODO BIG TODO!!!! + + Analyze the given experiment data. + + Args: + qubit: Index of qubit to analyze the result. + + Returns: + any: the output of the analysis, + """ + + metadata = experiment_data.data.header.metadata + + for idx, data in enumerate(experiment_data.data): + data = self.data_processor.format_data(data, data['metadata']) + + + qubit_data = experiment_data.groupby('qubit').get_group(qubit) + temp_results = dict() + + # fit for each initial guess + for xvals, yvals, series in self._get_target_data(qubit_data): + # fit for each series + best_result = None + for initial_guess in self.initial_guess(xvals, yvals): + # fit for each initial guess if there are many starting point + fit_result = optimize.minimize( + fun=self.chi_squared, + x0=initial_guess, + args=(xvals, yvals), + bounds=self.fit_boundary(xvals, yvals), + **kwargs + ) + if fit_result.success: + if not best_result or best_result.chisq > fit_result.fun: + best_result = FitResult( + fitvals=fit_result.x, + chisq=fit_result.fun, + xvals=xvals, + yvals=yvals + ) + else: + # fit failed, output log `fit_result.message` + pass + + # keep the best result + temp_results[series] = best_result + + # update analysis result + if self._result: + self._result[qubit] = temp_results + else: + self._result = {qubit: temp_results} + + return temp_results + + def _get_target_data(self, data: pd.DataFrame) -> Iterator[Tuple[np.ndarray, np.ndarray, str]]: + """ + Iterator to retrieve the series from the data. + + The user can generate arbitrary data sets to fit to. + Each data (xvals, yvals) in the data set should be returned as iterator + with the tagged name. Analysis `.run` method receives this data and perform + fitting. User can overwrite this method with respect to the data structure + that generator defines. + + Args: + data: Data source. The table has column of fit parameter names defined by + associated generator, series, experiment, and value. + + Yield: + Set of x-values and y-values with a string identifying the series. + """ + if len(self.x_values) > 1: + raise CalExpError('Default method does not support multi dimensional scan.') + + for series in data['series'].unique(): + xvals = np.array(data[data['series'] == series][self.x_values[0]]) + yvals = np.array(data[data['series'] == series]['value']) + + yield xvals, yvals, series diff --git a/qiskit_experiments/calibration/analysis/trigonometric.py b/qiskit_experiments/calibration/analysis/trigonometric.py new file mode 100644 index 0000000000..ba22be9f15 --- /dev/null +++ b/qiskit_experiments/calibration/analysis/trigonometric.py @@ -0,0 +1,76 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Trigonometric analysis.""" + +import numpy as np +from scipy import signal +from typing import Iterator, List, Tuple + +from .calibration_analysis import BaseCalibrationAnalysis + + +def _freq_guess(xvals: np.ndarray, yvals: np.ndarray): + """Initial frequency guess for oscillating data. + + Args: + xvals: The independent values. + yvals: The dependent values. + """ + + # Subtract DC component + fft_data = np.fft.fft(yvals - np.mean(yvals)) + fft_freq = np.fft.fftfreq(len(xvals), xvals[1] - xvals[0]) + + # Fit positive part of the spectrum + f0_guess = np.abs(fft_freq[np.argmax(np.abs(fft_data[0:len(fft_freq) // 2]))]) + + if f0_guess == 0: + # sampling duration is shorter than oscillation period + yvals = np.convolve(yvals, [0.5, 0.5], mode='same') + peaks, = signal.argrelmin(yvals, order=int(len(xvals) / 4)) + if len(peaks) == 0 or len(peaks) > 4: + return 0 + else: + return 1 / (2 * xvals[peaks[0]]) + + return f0_guess + + +class CosineFit(BaseCalibrationAnalysis): + r"""Fit with $F(x) = a \cos(2\pi f x + \phi) + b$.""" + + def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.ndarray]: + """Initial guess based on data. + + Args: + xvals: The independent values. + yvals: The dependent values. + """ + y_mean = np.mean(yvals) + a0 = np.max(np.abs(yvals)) - np.abs(y_mean) + f0 = max(0, _freq_guess(xvals, yvals)) + + for phi in np.linspace(-np.pi, np.pi, 10): + yield np.array([a0, f0, phi, y_mean]) + + def fit_function(self, xvals: np.ndarray, *args) -> np.ndarray: + """The cosine fit function. + + Args: + xvals: The values along the x-axis. + """ + return args[0] * np.cos(2 * np.pi * args[1] * xvals + args[2]) + args[3] + + def fit_boundary(self, xvals: np.ndarray, yvals: np.ndarray) -> List[Tuple[float, float]]: + """Allowed ranges for the parameters.""" + return [(-np.inf, np.inf), (0, np.inf), (-np.pi, np.pi), (-np.inf, np.inf)] diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py new file mode 100644 index 0000000000..ef0aeaa168 --- /dev/null +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -0,0 +1,362 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Class to store the results of a calibration experiments.""" + +import copy +import pandas as pd +from datetime import datetime +import dataclasses + +from typing import Tuple, Union, List, Optional, Type + +from qiskit.circuit import Gate +from qiskit import QiskitError, QuantumCircuit +from qiskit.pulse import Schedule, DriveChannel, ControlChannel, MeasureChannel +from qiskit.pulse.channels import PulseChannel +from qiskit.circuit import Parameter + +from .parameter_value import ParameterValue + + +class CalibrationsDefinition: + """ + A class to manage schedules with calibrated parameter values. + Schedules are stored in a dict and are intended to be fully parameterized, + including the index of the channels. The parameters are therefore stored in + the schedules. The names of the parameters must be unique. The calibrated + values of the parameters are stored in a dictionary. + """ + + def __init__(self, backend): + """ + Initialize the instructions from a given backend. + + Args: + backend: The backend from which to get the configuration. + """ + + self._n_qubits = backend.configuration().num_qubits + self._n_uchannels = backend.configuration().n_uchannels + self._config = backend.configuration() + self._params = {'qubit_freq': {}} + self._schedules = {} + self.backend = backend + + # Populate the qubit frequency estimates + now = datetime.now() + for qubit, freq in enumerate(backend.defaults().qubit_freq_est): + val = ParameterValue(freq, now) + self.add_parameter_value('qubit_freq', val, DriveChannel(qubit)) + + def schedules(self) -> pd.DataFrame: + """ + Return the schedules in self in a data frame to help + users manage their schedules. + + Returns: + data: A pandas data frame with all the schedules in it. + """ + data = [] + for name, schedule in self._schedules.items(): + data.append({'name': name, + 'schedule': schedule, + 'parameters': [param for param in schedule.parameters]}) + + return pd.DataFrame(data) + + def parameters(self, names: Optional[List[str]] = None, + chs: Optional[List[PulseChannel]] = None) -> pd.DataFrame: + """ + Returns the parameters as a pandas data frame. + This function is here to help users manage their parameters. + + Args: + names: The parameter names that should be included in the returned + table. If None is given then all names are included. + chs: The channels that should be included in the returned table. + If None is given then all channels are returned. + + Returns: + data: A data frame of parameter values. + """ + + data = [] + + if names is None: + names = self._params.keys() + + for name in names: + params_name = self._params.get(name, {}) + + if chs is None: + chs = params_name.keys() + + for ch in chs: + for value in params_name.get(ch, {}): + value_dict = dataclasses.asdict(value) + value_dict['channel'] = ch.name + value_dict['parameter'] = name + + data.append(value_dict) + + return pd.DataFrame(data) + + def add_schedules(self, schedules: Union[Schedule, List[Schedule]]): + """ + Add a schedule and register the parameters. + + Args: + schedules: The schedule to add. + """ + if isinstance(schedules, Schedule): + schedules = [schedules] + + for schedule in schedules: + + # check that channels, if parameterized, have the proper name format. + for ch in schedule.channels: + if isinstance(ch.index, Parameter): + try: + [int(_) for _ in ch.index.name.split('.')] + except ValueError: + raise QiskitError('Parameterized channel must have a name ' + 'formatted following index1.index2...') + + self._schedules[schedule.name] = schedule + + for param in schedule.parameters: + if param.name not in self._params: + self._params[param.name] = {} + + def add_parameter_value(self, param: Union[Parameter, str], + value: ParameterValue, + chs: Optional[Union[PulseChannel, List[PulseChannel]]] = None, + ch_type: Type[PulseChannel] = None): + """ + Add a parameter value to the stored parameters. This parameter value may be + applied to several channels, for instance, all DRAG pulses may have the same + standard deviation. The parameters are stored and identified by name. + + Args: + param: The parameter for which to add the measured value. + value: The value of the parameter to add. + chs: The channel(s) to which the parameter applies. If None is given + then the type of channels must by specified. + ch_type: This parameter is only used if chs is None. In this case the + value of the parameter will be set for all channels of the + specified type. + """ + if isinstance(param, Parameter): + name = param.name + else: + name = param + + if chs is None: + if ch_type is None: + raise QiskitError('Channel type must be given when chs are None.') + + if issubclass(ch_type, ControlChannel): + chs = [ch_type(_) for _ in range(self._n_uchannels)] + elif issubclass(ch_type, (DriveChannel, MeasureChannel)): + chs = [ch_type(_) for _ in range(self._n_qubits)] + else: + raise QiskitError('Unrecognised channel type {}.'.format(ch_type)) + + if not isinstance(chs, list): + chs = [chs] + + if name not in self._params: + raise QiskitError('Cannot add unknown parameter %s.' % name) + else: + for ch in chs: + if ch not in self._params[name]: + self._params[name][ch] = [value] + else: + self._params[name][ch].append(value) + + def get_channel_index(self, qubits: Tuple, ch: PulseChannel) -> int: + """ + Get the index of the parameterized channel based on the given qubits + and the name of the parameter in the channel index. The name of this + parameter must be written as qubit_index1.qubit_index2... . For example, + the following parameter names are valid: '1', '1.0', '3.10.0'. + + Args: + qubits: The qubits for which we want to obtain the channel index. + ch: The channel with a parameterized name. + + Returns: + index: The index of the channel. For example, if qubits=(int, int) and + the channel is a u channel with parameterized index name 'x.y' + where x and y the method returns the u_channel corresponding to + qubits (qubits[1], qubits[0]). + """ + + if isinstance(ch.index, Parameter): + indices = [int(_) for _ in ch.index.name.split('.')] + ch_qubits = tuple([qubits[_] for _ in indices]) + + if isinstance(ch, DriveChannel): + if len(ch_qubits) != 1: + raise QiskitError('Too many qubits for drive channel: ' + 'got %i expecting 1.' % len(ch_qubits)) + + ch_ = self._config.drive(ch_qubits[0]) + + elif isinstance(ch, MeasureChannel): + if len(ch_qubits) != 1: + raise QiskitError('Too many qubits for drive channel: ' + 'got %i expecting 1.' % len(ch_qubits)) + + ch_ = self._config.measure(ch_qubits[0]) + + elif isinstance(ch, ControlChannel): + chs_ = self._config.control(ch_qubits) + + if len(chs_) != 1: + raise QiskitError('Ambiguous number of control channels for ' + 'qubits {} and {}.'.format(qubits, ch.name)) + + ch_ = chs_[0] + + else: + chs = tuple(_.__name__ for _ in [DriveChannel, ControlChannel, MeasureChannel]) + raise QiskitError('Channel must be of type {}.'.format(chs)) + + return ch_.index + else: + return ch.index + + def parameter_value(self, name: str, ch: PulseChannel, valid_only: bool = True, + group: str = 'default', + cutoff_date: datetime = None) -> Union[int, float, complex]: + """ + Retrieve the value of a calibrated parameter from those stored. + + Args: + name: The name of the parameter to get. + ch: The channel for which we want the value of the parameter. + valid_only: Use only parameters marked as valid. + group: The calibration group from which to draw the + parameters. If not specifies this defaults to the 'default' group. + cutoff_date: Retrieve the most recent parameter up until the cutoff date. + Parameters generated after the cutoff date will be ignored. If the + cutoff_date is None then all parameters are considered. + + Returns: + value: The value of the parameter. + """ + + try: + if valid_only: + candidates = [p for p in self._params[name][ch] if p.valid] + else: + candidates = self._params[name][ch] + + candidates = [_ for _ in candidates if _.group == group] + + if cutoff_date: + candidates = [_ for _ in candidates if _ <= cutoff_date] + + candidates.sort(key=lambda x: x.date_time) + + return candidates[-1].value + except KeyError: + raise QiskitError('No parameter value for %s and channel %s' % (name, ch.name)) + + def get_schedule(self, name: str, qubits: Tuple[int, ...], + free_params: List[str] = None, group: Optional[str] = 'default') -> Schedule: + """ + Args: + name: The name of the schedule to get. + qubits: The qubits for which to get the schedule. + free_params: The parameters that should remain unassigned. + group: The calibration group from which to draw the + parameters. If not specifies this defaults to the 'default' group. + + Returns: + schedule: A deep copy of the template schedule with all parameters assigned + except for those specified by free_params. + + Raises: + QiskitError: if the name of the schedule is not known. + """ + + # Get the schedule and deepcopy it to prevent binding from removing + # the parametric nature of the schedule. + if name not in self._schedules: + raise QiskitError('Schedule %s is not defined.' % name) + + sched = copy.deepcopy(self._schedules[name]) + + # Retrieve the channel indices based on the qubits and bind them. + binding_dict = {} + for ch in sched.channels: + if ch.is_parameterized(): + index = self.get_channel_index(qubits, ch) + binding_dict[ch.index] = index + + sched.assign_parameters(binding_dict) + + # Loop through the remaining parameters in the schedule, get their values and bind. + if free_params is None: + free_params = [] + + binding_dict = {} + for inst in sched.instructions: + ch = inst[1].channel + for param in inst[1].parameters: + if param.name not in free_params: + binding_dict[param] = self.parameter_value(param.name, ch, group=group) + + sched.assign_parameters(binding_dict) + + return sched + + def get_circuit(self, name: str, qubits: Tuple, free_params: List[str] = None, + group: Optional[str] = 'default', schedule: Schedule = None) -> QuantumCircuit: + """ + Args: + name: The name of the gate to retrieve. + qubits: The qubits for which to generate the Gate. + free_params: Names of the parameters that will remain unassigned. + group: The calibration group from which to retrieve the calibrated values. + If unspecified this default to 'default'. + schedule: The schedule to add to the gate if the internally stored one is + not going to be used. + + Returns: + """ + if schedule is None: + schedule = self.get_schedule(name, qubits, free_params, group) + + gate = Gate(name=name, num_qubits=len(qubits), params=list(schedule.parameters)) + circ = QuantumCircuit(len(qubits), len(qubits)) + circ.append(gate, list(range(len(qubits)))) + circ.add_calibration(gate, qubits, schedule, params=schedule.parameters) + + return circ + + def to_db(self): + """ + Serializes the parameterized schedules and parameter values so + that they can be sent and stored in an external DB. + """ + raise NotImplementedError + + def from_db(self): + """ + Retrieves the parameterized schedules and pulse parameters from an + external DB. + """ + raise NotImplementedError diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/calibration/data_processing/__init__.py new file mode 100644 index 0000000000..d2d9c1fca2 --- /dev/null +++ b/qiskit_experiments/calibration/data_processing/__init__.py @@ -0,0 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +from .base import DataAction +from .nodes import SystemKernel +from .nodes import SystemDiscriminator +from .data_processor import DataProcessor diff --git a/qiskit_experiments/calibration/data_processing/base.py b/qiskit_experiments/calibration/data_processing/base.py new file mode 100644 index 0000000000..a732a1453b --- /dev/null +++ b/qiskit_experiments/calibration/data_processing/base.py @@ -0,0 +1,140 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Defines the steps that can be used to analyse data.""" + +from abc import ABCMeta, abstractmethod +from enum import Enum +from typing import Any + +from qiskit_experiments.calibration.metadata import CalibrationMetadata +from qiskit_experiments.calibration.exceptions import CalibrationError + + +class DataAction(metaclass=ABCMeta): + """ + Abstract action which is a single action done on measured data to process it. + Each subclass of DataAction must define the type of data that it accepts as input + using decorators. + """ + + node_type = None + prev_node = () + + def __init__(self): + """Create new data analysis routine.""" + self._child = None + + @property + def child(self) -> 'DataAction': + """Return the child of this data processing step.""" + return self._child + + def append(self, component: 'DataAction'): + """Add new data processing routine. + + Args: + component: New data processing routine. + + Raises: + CalibrationError: If the previous node is None (i.e. a root node) + """ + if not component.prev_node: + raise CalibrationError(f'Analysis routine {component.__class__.__name__} is a root' + f'node. This routine cannot be appended to another node.') + + if self._child is None: + if isinstance(self, component.prev_node): + self._child = component + else: + raise CalibrationError(f'Analysis routine {component.__class__.__name__} ' + f'cannot be appended after {self.__class__.__name__}') + else: + self._child.append(component) + + @abstractmethod + def process(self, data: Any, **kwargs): + """ + TODO + + Applies the data processing step to the data. + + Args: + data: + kwargs: + """ + + def format_data(self, data: Any, metadata: CalibrationMetadata, shots: int) -> Any: + """ + TODO + + Args: + data: + metadata: + shots: + + Returns: + processed_data: + """ + processed_data = self.process(data, metadata=metadata, shots=shots) + + if self._child: + return self._child.format_data(processed_data, metadata, shots) + else: + return processed_data + + +class NodeType(Enum): + """Type of node that can be supported by the analysis steps.""" + KERNEL = 1 + DISCRIMINATOR = 2 + IQDATA = 3 + COUNTS = 4 + + +def kernel(cls: DataAction): + """A decorator to give kernel attribute to node.""" + cls.node_type = NodeType.KERNEL + return cls + + +def discriminator(cls: DataAction): + """A decorator to give discriminator attribute to node.""" + cls.node_type = NodeType.DISCRIMINATOR + return cls + + +def iq_data(cls: DataAction): + """A decorator to give iqdata attribute to node.""" + cls.node_type = NodeType.IQDATA + return cls + + +def counts(cls: DataAction): + """A decorator to give counts attribute to node.""" + cls.node_type = NodeType.COUNTS + return cls + + +def prev_node(*nodes: DataAction): + """A decorator to specify the available previous nodes.""" + + try: + nodes = list(nodes) + except TypeError: + nodes = [nodes] + + def add_nodes(cls: DataAction): + cls.prev_node = tuple(nodes) + return cls + + return add_nodes diff --git a/qiskit_experiments/calibration/data_processing/data_processor.py b/qiskit_experiments/calibration/data_processing/data_processor.py new file mode 100644 index 0000000000..ebdc5f03b2 --- /dev/null +++ b/qiskit_experiments/calibration/data_processing/data_processor.py @@ -0,0 +1,162 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Class that ties together data processing steps.""" + +import numpy as np +from typing import Union + +from .nodes import SystemKernel, SystemDiscriminator +from .base import NodeType, DataAction +from qiskit_experiments.calibration.metadata import CalibrationMetadata +from qiskit_experiments.calibration.exceptions import CalibrationError +from qiskit.qobj.utils import MeasLevel, MeasReturnType +from qiskit.result import Result + + +class DataProcessor: + """ + Defines the actions done on the measured data to bring it in a form usable + by the calibration analysis classes. + """ + + def __init__(self, average: bool = True): + """Create an empty chain of data ProcessingSteps. + + Args: + average: Set `True` to average outcomes. + """ + self._average = average + self._root_node = None + self._shots = None + + @property + def shots(self): + """Return the number of shots.""" + return self._shots + + @shots.setter + def shots(self, val: int): + """Set new shot value.""" + self._shots = val + + def append(self, node: DataAction): + """ + Append new data action node to this data processor. + + Args: + node: A DataAction that will process the data. + """ + if self._root_node: + self._root_node.append(node) + else: + self._root_node = node + + def meas_return(self) -> MeasReturnType: + """ + Returns: + MeasReturnType: The appropriate measurement format to use this analysis chain. + """ + if DataProcessor.check_discriminator(self._root_node): + # if discriminator is defined, return type should be single. + # quantum state cannot be discriminated with averaged IQ coordinate. + return MeasReturnType.SINGLE + + return MeasReturnType.AVERAGE if self._average else MeasReturnType.SINGLE + + def meas_level(self) -> MeasLevel: + """ + TODO What about starting from MeasLevel 1? + + Returns: + measurement level: MeasLevel.CLASSIFIED is returned if the end data is discriminated, + MeasLevel.KERNELED is returned if a kernel is defined but no discriminator, and + MeasLevel.RAW is returned is no kernel is defined. + """ + kernel = DataProcessor.check_kernel(self._root_node) + if kernel and isinstance(kernel, SystemKernel): + discriminator = DataProcessor.check_discriminator(self._root_node) + if discriminator and isinstance(discriminator, SystemDiscriminator): + + # classified level if both system kernel and discriminator are defined + return MeasLevel.CLASSIFIED + + # kerneled level if only system kernel is defined + return MeasLevel.KERNELED + + # otherwise raw level is requested + return MeasLevel.RAW + + def format_data(self, result: Result, metadata: CalibrationMetadata, index: int): + """ + Format Qiskit result data. + + This method sequentially calls stored child data processing nodes + with its `format_data` methods. Once all child nodes have called, + input data is converted into expected data format. + + Args: + result: Qiskit Result object. + metadata: Metadata for the target circuit. + index: Index of target circuit in the experiment. + + Raises: + CalibrationError: if + """ + if not self._root_node: + return result + + # extract outcome with marginalize. note that the pulse experiment data + # is not marginalized on the backend. + + if self.meas_level() == MeasLevel.CLASSIFIED: + data = result.get_counts(experiment=index) + + elif self.meas_level() == MeasLevel.KERNELED: + data = np.asarray(result.get_memory(index), dtype=complex) + + elif self.meas_level() == MeasLevel.RAW: + raise CalibrationError('Raw data analysis is not supported.') + + else: + raise CalibrationError('Invalid measurement level is specified.') + + if not self._root_node: + return data + + return self._root_node.format_data(data, metadata=metadata, shots=self.shots) + + @classmethod + def check_kernel(cls, node: DataAction) -> Union[None, DataAction]: + """Return the stored kernel in the workflow.""" + if not node: + return None + + if node.node_type == NodeType.KERNEL: + return node + else: + if not node.child: + return None + return cls.check_kernel(node.child) + + @classmethod + def check_discriminator(cls, node: DataAction): + """Return stored discriminator in the workflow.""" + if not node: + return None + + if node.node_type == NodeType.DISCRIMINATOR: + return node + else: + if not node.child: + return None + return cls.check_discriminator(node.child) diff --git a/qiskit_experiments/calibration/data_processing/nodes.py b/qiskit_experiments/calibration/data_processing/nodes.py new file mode 100644 index 0000000000..73b4065c0e --- /dev/null +++ b/qiskit_experiments/calibration/data_processing/nodes.py @@ -0,0 +1,143 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Different data analysis steps.""" + +from typing import Optional, Any, Union + +import numpy as np +from . import base +from . import DataAction +from qiskit.result.counts import Counts + + +@base.kernel +@base.prev_node() +class SystemKernel(DataAction): + """Backend system kernel.""" + + def __init__(self, name: Optional[str] = None): + self.name = name + super().__init__() + + def process(self, data: Any, **kwargs) -> Union[float, np.ndarray]: + """ + Args: + data: + + Returns: + data: The data after applying the integration kernel. + """ + raise NotImplementedError + + +@base.discriminator +@base.prev_node(SystemKernel) +class SystemDiscriminator(DataAction): + """Backend system discriminator.""" + + def __init__(self, discriminator, name: Optional[str] = None): + """ + Args: + discriminator: The discriminator used to transform the data to counts. + For example, transform IQ data to counts. + """ + self.discriminator = discriminator + self.name = name + super().__init__() + + def process(self, data: Union[float, np.ndarray], **kwargs) -> Counts: + """ + Discriminate the data to transform it into counts. + + Args: + data: The data in a format that can be understood by the discriminator. + """ + return self.discriminator.discriminate(data) + + +@base.iq_data +@base.prev_node(SystemKernel) +class ToReal(DataAction): + """IQ data post-processing. This returns real part of IQ data.""" + + def __init__(self, scale: Optional[float] = 1.0): + """ + Args: + scale: scale by which to multiply the real part of the data. + """ + self.scale = scale + super().__init__() + + def process(self, data: Union[float, np.ndarray], **kwargs): + """ + Scales the real part of IQ data. + + Args: + data: IQ Data. + + Returns: + data: The scaled imaginary part of the data. + """ + return self.scale * data.real + + +@base.iq_data +@base.prev_node(SystemKernel) +class ToImag(DataAction): + """IQ data post-processing. This returns imaginary part of IQ data.""" + + def __init__(self, scale: Optional[float] = 1.0): + """ + Args: + scale: scale by which to multiply the imaginary part of the data. + """ + self.scale = scale + super().__init__() + + def process(self, data: Union[float, np.ndarray], **kwargs) -> Union[float, np.ndarray]: + """ + Scales the imaginary part of IQ data. + + Args: + data: IQ Data + + Returns: + data: The scaled imaginary part of the data. + """ + return self.scale * data.imag + + +@base.counts +@base.prev_node(SystemDiscriminator) +class Population(DataAction): + """Count data post processing. This returns population.""" + + def process(self, data: Counts, **kwargs): + """ + Args: + data: in Count format. + + Returns: + populations: The counts divided by the number of shots. + """ + + populations = np.zeros(len(list(data.keys())[0])) + + shots = 0 + for bit_str, count in data.items(): + shots += 1 + for ind, bit in enumerate(bit_str): + if bit == '1': + populations[ind] += count + + return populations / shots diff --git a/qiskit_experiments/calibration/exceptions.py b/qiskit_experiments/calibration/exceptions.py new file mode 100644 index 0000000000..4d9f45bf3c --- /dev/null +++ b/qiskit_experiments/calibration/exceptions.py @@ -0,0 +1,28 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Exceptions for calibration.""" + +from qiskit.exceptions import QiskitError + + +class CalibrationError(QiskitError): + """Errors raised by the calibration module.""" + + def __init__(self, *message): + """Set the error message.""" + super().__init__(*message) + self.message = ' '.join(message) + + def __str__(self): + """Return the message.""" + return repr(self.message) diff --git a/qiskit_experiments/calibration/experiments/__init__.py b/qiskit_experiments/calibration/experiments/__init__.py new file mode 100644 index 0000000000..0ecbce0962 --- /dev/null +++ b/qiskit_experiments/calibration/experiments/__init__.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""All the experiments used to calibrate gates.""" \ No newline at end of file diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py new file mode 100644 index 0000000000..8736ff45ac --- /dev/null +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -0,0 +1,29 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Rough amplitude calibration.""" + +from qiskit_experiments.base_experiment import BaseExperiment + + +class RoughAmplitude(BaseExperiment): + + # Analysis class for experiment + __analysis_class__ = None + + def __init__(self): + + qubits = None + super.__init__(qubits, 'rough_amplitude_calibration') + + def circuits(self, backend=None, **circuit_options): + """""" diff --git a/qiskit_experiments/calibration/metadata.py b/qiskit_experiments/calibration/metadata.py new file mode 100644 index 0000000000..2f4d6f7476 --- /dev/null +++ b/qiskit_experiments/calibration/metadata.py @@ -0,0 +1,62 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Class that defines the structure of the metadata for calibration experiments.""" + +from dataclasses import dataclass +from typing import Dict, List, Union + + +@dataclass +class CalibrationMetadata: + """ + Defines the structure of the meta data that describes + calibration experiments. Calibration analysis routines will + use variables of this class to tie together the results from + different quantum circuits. + """ + + # The name of the calibration experiment. + name: str = None + + # Name of the pulse schedule that was used in the calibration experiment. + pulse_schedule_name: str = None + + # A dictionary of x-values the structure of this dict will + # depend on the experiment being run. + x_values: Dict[str, Union[int, float, complex]] = None + + # The series of the Experiment to which the circuit is + # attached to. E.g. 'X' or 'Y' for Ramsey measurements. + series: Union[str, int, float] = None + + # ID of the experiment to which this circuit is attached. + exp_id: str = None + + # Physical qubits used. + qubits: List[int] = None + + # Mapping of qubit index and classical register index. + # The key is the qubit index and value is the classical bit index. + # This mapping is automatically generated at BaseCalibrationExperiment class. + register_map: Dict[int, int] = None + + def __post_init__(self): + """Ensure that keys are integers. This is needed because in JSon keys are str.""" + if not self.register_map: + return + + register_map = {} + for key, value in self.register_map.items(): + register_map[int(key)] = int(value) + + self.register_map = register_map diff --git a/qiskit_experiments/calibration/parameter_value.py b/qiskit_experiments/calibration/parameter_value.py new file mode 100644 index 0000000000..28a86f62a0 --- /dev/null +++ b/qiskit_experiments/calibration/parameter_value.py @@ -0,0 +1,36 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Data class for parameter values.""" + +from dataclasses import dataclass +from datetime import datetime +from typing import Union + + +@dataclass +class ParameterValue: + + # Value assumed by the parameter + value: Union[int, float] = None + + # Data time when the value of the parameter was generated + date_time: datetime = datetime.fromtimestamp(0) + + # An enum indicating if the parameter is valid + valid: bool = True + + # The experiment from which the value of this parameter was generated. + exp_id: str = None + + # The group of calibrations to which this parameter belongs + group: str = 'default' diff --git a/test/calibration/__init__.py b/test/calibration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/calibration/test_data_processing.py b/test/calibration/test_data_processing.py new file mode 100644 index 0000000000..2340d0ae19 --- /dev/null +++ b/test/calibration/test_data_processing.py @@ -0,0 +1,58 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Data processor tests.""" + +from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments.calibration.metadata import CalibrationMetadata +from qiskit_experiments.calibration.data_processing import SystemKernel, SystemDiscriminator +from qiskit.result import models +from qiskit.result import Result +from qiskit.test import QiskitTestCase +from qiskit.qobj.utils import MeasLevel + + +class DataProcessorTest(QiskitTestCase): + """Class to test DataProcessor.""" + + def setUp(self): + self.base_result_args = dict(backend_name='test_backend', + backend_version='1.0.0', + qobj_id='id-123', + job_id='job-123', + success=True) + + super().setUp() + + def test_empty_processor(self): + """Check that a DataProcessor without steps does nothing.""" + + raw_counts = {'0x0': 4, '0x2': 10} + data = models.ExperimentResultData(counts=dict(**raw_counts)) + exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data) + result = Result(results=[exp_result], **self.base_result_args) + + data_processor = DataProcessor() + processed = data_processor.format_data(result, metadata=CalibrationMetadata(), index=0) + self.assertEqual(processed.get_counts(0)['0'], 4) + self.assertEqual(processed.get_counts(0)['10'], 10) + + def test_append_kernel(self): + """Tests that we can add a kernel and a discriminator.""" + processor = DataProcessor() + self.assertEqual(processor.meas_level(), MeasLevel.RAW) + + processor.append(SystemKernel()) + self.assertEqual(processor.meas_level(), MeasLevel.KERNELED) + + processor.append(SystemDiscriminator(None)) + self.assertEqual(processor.meas_level(), MeasLevel.CLASSIFIED) From 571b08fce890a9a0a8b8ad7a8fa2098c3c65b8e4 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:38:43 +0100 Subject: [PATCH 02/32] * Added new files. * Refactored data processing package. --- .../calibration/analysis/fit_result.py | 0 .../calibration/analysis/utils.py | 0 .../calibration/data_processing/__init__.py | 4 + .../calibration/data_processing/base.py | 31 +++--- .../data_processing/data_processor.py | 70 +++++------- .../calibration/data_processing/nodes.py | 103 +++++++++++++----- .../data_processing/processed_data.py | 49 +++++++++ test/calibration/test_analysis.py | 0 .../test_calibrations_definition.py | 0 test/calibration/test_experiments.py | 17 +++ 10 files changed, 189 insertions(+), 85 deletions(-) create mode 100644 qiskit_experiments/calibration/analysis/fit_result.py create mode 100644 qiskit_experiments/calibration/analysis/utils.py create mode 100644 qiskit_experiments/calibration/data_processing/processed_data.py create mode 100644 test/calibration/test_analysis.py create mode 100644 test/calibration/test_calibrations_definition.py create mode 100644 test/calibration/test_experiments.py diff --git a/qiskit_experiments/calibration/analysis/fit_result.py b/qiskit_experiments/calibration/analysis/fit_result.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qiskit_experiments/calibration/analysis/utils.py b/qiskit_experiments/calibration/analysis/utils.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/calibration/data_processing/__init__.py index d2d9c1fca2..fed8744117 100644 --- a/qiskit_experiments/calibration/data_processing/__init__.py +++ b/qiskit_experiments/calibration/data_processing/__init__.py @@ -13,4 +13,8 @@ from .base import DataAction from .nodes import SystemKernel from .nodes import SystemDiscriminator +from .nodes import ToReal +from .nodes import ToImag +from .nodes import Population from .data_processor import DataProcessor +from .processed_data import ProcessedData diff --git a/qiskit_experiments/calibration/data_processing/base.py b/qiskit_experiments/calibration/data_processing/base.py index a732a1453b..dab31fac76 100644 --- a/qiskit_experiments/calibration/data_processing/base.py +++ b/qiskit_experiments/calibration/data_processing/base.py @@ -14,9 +14,8 @@ from abc import ABCMeta, abstractmethod from enum import Enum -from typing import Any +from typing import Any, Dict -from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments.calibration.exceptions import CalibrationError @@ -62,33 +61,30 @@ def append(self, component: 'DataAction'): self._child.append(component) @abstractmethod - def process(self, data: Any, **kwargs): + def process(self, data: Dict[str, Any]): """ - TODO - Applies the data processing step to the data. Args: - data: - kwargs: + data: the data to which the data processing step will be applied. """ - def format_data(self, data: Any, metadata: CalibrationMetadata, shots: int) -> Any: + def format_data(self, data: Dict[str, Any]) -> Any: """ - TODO + Apply the data action of this node and call the child node's format_data method. Args: - data: - metadata: - shots: + data: A dict containing the data. The action nodes in the data + processor will raise errors if the data does not contain the + appropriate data. Returns: processed_data: """ - processed_data = self.process(data, metadata=metadata, shots=shots) + processed_data = self.process(data) if self._child: - return self._child.format_data(processed_data, metadata, shots) + return self._child.format_data(processed_data) else: return processed_data @@ -99,6 +95,7 @@ class NodeType(Enum): DISCRIMINATOR = 2 IQDATA = 3 COUNTS = 4 + POPULATION = 5 def kernel(cls: DataAction): @@ -125,6 +122,12 @@ def counts(cls: DataAction): return cls +def population(cls: DataAction): + """A decorator to give population attribute to node.""" + cls.node_type = NodeType.POPULATION + return cls + + def prev_node(*nodes: DataAction): """A decorator to specify the available previous nodes.""" diff --git a/qiskit_experiments/calibration/data_processing/data_processor.py b/qiskit_experiments/calibration/data_processing/data_processor.py index ebdc5f03b2..6c686324a6 100644 --- a/qiskit_experiments/calibration/data_processing/data_processor.py +++ b/qiskit_experiments/calibration/data_processing/data_processor.py @@ -10,17 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Class that ties together data processing steps.""" +"""Class that ties together actions on the data.""" -import numpy as np -from typing import Union +from typing import Any, Dict, Union from .nodes import SystemKernel, SystemDiscriminator from .base import NodeType, DataAction -from qiskit_experiments.calibration.metadata import CalibrationMetadata -from qiskit_experiments.calibration.exceptions import CalibrationError from qiskit.qobj.utils import MeasLevel, MeasReturnType -from qiskit.result import Result class DataProcessor: @@ -32,22 +28,13 @@ class DataProcessor: def __init__(self, average: bool = True): """Create an empty chain of data ProcessingSteps. + TODO self._average is not used. + Args: average: Set `True` to average outcomes. """ self._average = average self._root_node = None - self._shots = None - - @property - def shots(self): - """Return the number of shots.""" - return self._shots - - @shots.setter - def shots(self, val: int): - """Set new shot value.""" - self._shots = val def append(self, node: DataAction): """ @@ -75,8 +62,6 @@ def meas_return(self) -> MeasReturnType: def meas_level(self) -> MeasLevel: """ - TODO What about starting from MeasLevel 1? - Returns: measurement level: MeasLevel.CLASSIFIED is returned if the end data is discriminated, MeasLevel.KERNELED is returned if a kernel is defined but no discriminator, and @@ -96,7 +81,24 @@ def meas_level(self) -> MeasLevel: # otherwise raw level is requested return MeasLevel.RAW - def format_data(self, result: Result, metadata: CalibrationMetadata, index: int): + def output_key(self) -> str: + """Return the key to look for in the data output by the processor.""" + if self._root_node: + node = self._root_node + while node.child: + node = node.child + + if node.node_type in [NodeType.KERNEL, NodeType.IQDATA]: + return 'memory' + if node.node_type == NodeType.DISCRIMINATOR: + return 'counts' + if node.node_type == NodeType.POPULATION: + return 'populations' + + return 'counts' + + + def format_data(self, data: Dict[str, Any]): """ Format Qiskit result data. @@ -105,35 +107,13 @@ def format_data(self, result: Result, metadata: CalibrationMetadata, index: int) input data is converted into expected data format. Args: - result: Qiskit Result object. - metadata: Metadata for the target circuit. - index: Index of target circuit in the experiment. - - Raises: - CalibrationError: if + data: The data, typically from an ExperimentData instance, that needs to + be processed. This dict also contains the metadata of each experiment. """ - if not self._root_node: - return result - - # extract outcome with marginalize. note that the pulse experiment data - # is not marginalized on the backend. - - if self.meas_level() == MeasLevel.CLASSIFIED: - data = result.get_counts(experiment=index) - - elif self.meas_level() == MeasLevel.KERNELED: - data = np.asarray(result.get_memory(index), dtype=complex) - - elif self.meas_level() == MeasLevel.RAW: - raise CalibrationError('Raw data analysis is not supported.') - - else: - raise CalibrationError('Invalid measurement level is specified.') - if not self._root_node: return data - return self._root_node.format_data(data, metadata=metadata, shots=self.shots) + return self._root_node.format_data(data) @classmethod def check_kernel(cls, node: DataAction) -> Union[None, DataAction]: diff --git a/qiskit_experiments/calibration/data_processing/nodes.py b/qiskit_experiments/calibration/data_processing/nodes.py index 73b4065c0e..010b5ff4e2 100644 --- a/qiskit_experiments/calibration/data_processing/nodes.py +++ b/qiskit_experiments/calibration/data_processing/nodes.py @@ -12,12 +12,12 @@ """Different data analysis steps.""" -from typing import Optional, Any, Union +from typing import Any, Dict, List, Optional, Union import numpy as np from . import base from . import DataAction -from qiskit.result.counts import Counts +from qiskit_experiments.calibration.exceptions import CalibrationError @base.kernel @@ -37,7 +37,7 @@ def process(self, data: Any, **kwargs) -> Union[float, np.ndarray]: Returns: data: The data after applying the integration kernel. """ - raise NotImplementedError + return data @base.discriminator @@ -55,14 +55,21 @@ def __init__(self, discriminator, name: Optional[str] = None): self.name = name super().__init__() - def process(self, data: Union[float, np.ndarray], **kwargs) -> Counts: + def process(self, data: Dict[str, Any]): """ Discriminate the data to transform it into counts. Args: data: The data in a format that can be understood by the discriminator. + + Raises: + CalibrationError: if the data does not contain memory. """ - return self.discriminator.discriminate(data) + if 'memory' not in data: + raise CalibrationError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') + + data['counts'] = self.discriminator.discriminate(np.array(data['memory'])) @base.iq_data @@ -70,74 +77,118 @@ def process(self, data: Union[float, np.ndarray], **kwargs) -> Counts: class ToReal(DataAction): """IQ data post-processing. This returns real part of IQ data.""" - def __init__(self, scale: Optional[float] = 1.0): + def __init__(self, scale: Optional[float] = 1.0, average: bool = False): """ Args: scale: scale by which to multiply the real part of the data. + average: if True the single-shots are averaged. """ self.scale = scale + self.average = average super().__init__() - def process(self, data: Union[float, np.ndarray], **kwargs): + def process(self, data: Dict[str, Any], **kwargs): """ - Scales the real part of IQ data. + Modifies the data inplace by taking the real part of the memory and + scaling it by the given factor. Args: - data: IQ Data. + data: The data dict. IQ data is stored under memory. - Returns: - data: The scaled imaginary part of the data. + Raises: + CalibrationError: if the data does not contain memory. """ - return self.scale * data.real + if 'memory' not in data: + raise CalibrationError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') + + # Single shot data + if isinstance(data['memory'][0][0], List): + new_mem = [] + for shot_idx, shot in enumerate(data['memory']): + new_mem.append([self.scale*_[0] for _ in shot]) + if self.average: + new_mem = list(np.mean(np.array(new_mem), axis=0)) + + # Averaged data + else: + new_mem = [self.scale*_[0] for _ in data['memory']] + + data['memory'] = new_mem @base.iq_data @base.prev_node(SystemKernel) class ToImag(DataAction): """IQ data post-processing. This returns imaginary part of IQ data.""" - def __init__(self, scale: Optional[float] = 1.0): + def __init__(self, scale: Optional[float] = 1.0, average: bool = False): """ Args: scale: scale by which to multiply the imaginary part of the data. """ self.scale = scale + self.average = average super().__init__() - def process(self, data: Union[float, np.ndarray], **kwargs) -> Union[float, np.ndarray]: + def process(self, data: Dict[str, Any], **kwargs): """ Scales the imaginary part of IQ data. Args: - data: IQ Data + data: The data dict. IQ data is stored under memory. - Returns: - data: The scaled imaginary part of the data. + Raises: + CalibrationError: if the data does not contain memory. """ - return self.scale * data.imag + if 'memory' not in data: + raise CalibrationError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') + # Single shot data + if isinstance(data['memory'][0][0], List): + new_mem = [] + for shot_idx, shot in enumerate(data['memory']): + new_mem.append([self.scale*_[1] for _ in shot]) -@base.counts + if self.average: + new_mem = list(np.mean(np.array(new_mem), axis=0)) + + # Averaged data + else: + new_mem = [self.scale*_[0] for _ in data['memory']] + + data['memory'] = new_mem + + +@base.population @base.prev_node(SystemDiscriminator) class Population(DataAction): """Count data post processing. This returns population.""" - def process(self, data: Counts, **kwargs): + def process(self, data: Dict[str, Any]): """ Args: - data: in Count format. + data: The data dictionary. This will modify the dict in place, + taking the data under counts and adding the corresponding + populations. - Returns: - populations: The counts divided by the number of shots. + Raises: + CalibrationError: if counts are not in the given data. """ + if 'counts' not in data: + raise CalibrationError(f'Data does not have counts. ' + f'Cannot apply {self.__class__.__name__}') + + counts = data.get('counts') - populations = np.zeros(len(list(data.keys())[0])) + populations = np.zeros(len(list(counts.keys())[0])) shots = 0 - for bit_str, count in data.items(): + for bit_str, count in counts.items(): shots += 1 for ind, bit in enumerate(bit_str): if bit == '1': populations[ind] += count - return populations / shots + data['populations'] = populations / shots diff --git a/qiskit_experiments/calibration/data_processing/processed_data.py b/qiskit_experiments/calibration/data_processing/processed_data.py new file mode 100644 index 0000000000..eccea97214 --- /dev/null +++ b/qiskit_experiments/calibration/data_processing/processed_data.py @@ -0,0 +1,49 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Light-weight class of analysis ready data.""" + +from typing import Iterator +import numpy as np + + +class ProcessedData: + """ProcessedData is a light-weight class of data ready for analysis.""" + + def __init__(self): + """Setup the empty data container.""" + self._data = {} + + def add_data_point(self, xval, yval, series: str = None): + """ + Args: + xval: The value of the independent variable. + yval: The value of the dependent variable. + series: The series to which this data point belongs. + """ + + if series is None: + series = 'default' + + if series not in self._data: + self._data[series] = {'xvals': [], 'yvals': []} + + self._data[series]['xvals'].append(xval) + self._data[series]['yvals'].append(yval) + + def series(self) -> Iterator: + """ + Returns: + iterator: where the return values are tuples of (xvals, yvals, series) + """ + for series, data in self._data.items(): + yield np.array(data['xvals']), np.array(data['yvals']), series diff --git a/test/calibration/test_analysis.py b/test/calibration/test_analysis.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/calibration/test_calibrations_definition.py b/test/calibration/test_calibrations_definition.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/calibration/test_experiments.py b/test/calibration/test_experiments.py new file mode 100644 index 0000000000..bc5b39ccf7 --- /dev/null +++ b/test/calibration/test_experiments.py @@ -0,0 +1,17 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the rough amplitude experiment.""" + +class TestRoughAmplitude: + + \ No newline at end of file From c4d7c53c4df8891b030d44555b9989ec612ad0e6 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:39:35 +0100 Subject: [PATCH 03/32] * Changes to calibration module init. --- qiskit_experiments/calibration/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qiskit_experiments/calibration/__init__.py b/qiskit_experiments/calibration/__init__.py index abf2396e56..03a4a420a3 100644 --- a/qiskit_experiments/calibration/__init__.py +++ b/qiskit_experiments/calibration/__init__.py @@ -13,3 +13,6 @@ """Qiskit calibration root.""" from .data_processing import DataProcessor +from .calibration_definitions import CalibrationsDefinition +from .parameter_value import ParameterValue +from .experiments import RoughAmplitude From ed949232c135ccc4d6dbe7ec3704552ccbeeb234 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:40:30 +0100 Subject: [PATCH 04/32] * Added FitResults to analysis. * Added body or _run_analysis. * Added plotting to BaseCalibrationAnalysis. --- .../calibration/analysis/__init__.py | 2 + .../analysis/calibration_analysis.py | 150 ++++++++++-------- .../calibration/analysis/fit_result.py | 43 +++++ .../calibration/analysis/utils.py | 50 ++++++ 4 files changed, 176 insertions(+), 69 deletions(-) diff --git a/qiskit_experiments/calibration/analysis/__init__.py b/qiskit_experiments/calibration/analysis/__init__.py index 96c0cf22be..6044666f74 100644 --- a/qiskit_experiments/calibration/analysis/__init__.py +++ b/qiskit_experiments/calibration/analysis/__init__.py @@ -9,3 +9,5 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. + +from .trigonometric import CosineFit diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 0f9e5ba800..e4b2832df3 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -14,45 +14,22 @@ import numpy as np from abc import abstractmethod -from typing import Iterator, List, Optional, Tuple +from typing import Iterator, List, Tuple +from .fit_result import FitResult from qiskit_experiments.base_analysis import BaseAnalysis from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments import ExperimentData +from qiskit_experiments.calibration.data_processing.processed_data import ProcessedData +from qiskit.qobj.utils import MeasReturnType +from qiskit_experiments.experiment_data import AnalysisResult +from scipy import optimize class BaseCalibrationAnalysis(BaseAnalysis): """Abstract base class for all calibration analysis classes.""" - def __init__(self, experiment_data, - data_processor: Optional[DataProcessor] = None, - **options): - """Run analysis on circuit data. - - Args: - experiment_data (ExperimentData): the experiment data to analyze. - data_processor: Specifies the actions to apply when processing the measured data. - options: kwarg options for analysis function. - - Returns: - tuple: A pair ``(analysis_results, figures)`` where - ``analysis_results`` may be a single or list of - AnalysisResult objects, and ``figures`` may be - None, a single figure, or a list of figures. - """ - - self._data_processor = data_processor - - @property - def data_processor(self) -> DataProcessor: - """Return the data processor.""" - return self._data_processor - - @data_processor.setter - def data_processor(self, data_processor: DataProcessor): - """Set the data processor.""" - self._data_processor = data_processor - @abstractmethod def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.ndarray]: """Create initial guess for fit parameters. @@ -105,31 +82,42 @@ def chi_squared(self, return chi_sq / dof - def _run_analysis(self, experiment_data: ExperimentData, **kwargs) -> any: + def _run_analysis(self, experiment_data: ExperimentData, qubit=0, + data_processor=DataProcessor(), plot=False, **kwargs) -> any: """ - TODO BIG TODO!!!! Analyze the given experiment data. + Notes: data is a List of Dict. + Args: - qubit: Index of qubit to analyze the result. + experiment_data: The data to analyse. Returns: any: the output of the analysis, """ - metadata = experiment_data.data.header.metadata + # 1) Format the data using the DataProcessor for the analysis. + for exp_idx, data in enumerate(experiment_data.data): + data_processor.format_data(data) - for idx, data in enumerate(experiment_data.data): - data = self.data_processor.format_data(data, data['metadata']) + # 2) Extract series information from the data + key = data_processor.output_key() + meas_return = data_processor.meas_return() + series = ProcessedData() + for exp_idx, data in enumerate(experiment_data.data): + metadata = CalibrationMetadata(**data['metadata']) + if meas_return == MeasReturnType.AVERAGE: + yval = data[key][qubit] + else: + yval = [data[key][_][qubit] for _ in range(len(data[key]))] - qubit_data = experiment_data.groupby('qubit').get_group(qubit) - temp_results = dict() + series.add_data_point(metadata.x_values, yval, metadata.series) - # fit for each initial guess - for xvals, yvals, series in self._get_target_data(qubit_data): - # fit for each series + # 3) Fit the data for each initial guess and series. + results = AnalysisResult() + for xvals, yvals, series in series.series(): best_result = None for initial_guess in self.initial_guess(xvals, yvals): # fit for each initial guess if there are many starting point @@ -137,9 +125,9 @@ def _run_analysis(self, experiment_data: ExperimentData, **kwargs) -> any: fun=self.chi_squared, x0=initial_guess, args=(xvals, yvals), - bounds=self.fit_boundary(xvals, yvals), - **kwargs + bounds=self.fit_boundary(xvals, yvals) ) + if fit_result.success: if not best_result or best_result.chisq > fit_result.fun: best_result = FitResult( @@ -153,38 +141,62 @@ def _run_analysis(self, experiment_data: ExperimentData, **kwargs) -> any: pass # keep the best result - temp_results[series] = best_result + results[series] = best_result - # update analysis result - if self._result: - self._result[qubit] = temp_results - else: - self._result = {qubit: temp_results} + experiment_data.add_analysis_result(results) - return temp_results + figures = None + if plot: + figures = self._plot(qubit, results, **kwargs) - def _get_target_data(self, data: pd.DataFrame) -> Iterator[Tuple[np.ndarray, np.ndarray, str]]: - """ - Iterator to retrieve the series from the data. + return results, figures - The user can generate arbitrary data sets to fit to. - Each data (xvals, yvals) in the data set should be returned as iterator - with the tagged name. Analysis `.run` method receives this data and perform - fitting. User can overwrite this method with respect to the data structure - that generator defines. + def _plot(self, qubit, results, **kwargs): + """ + Make plots of the results. Args: - data: Data source. The table has column of fit parameter names defined by - associated generator, series, experiment, and value. - - Yield: - Set of x-values and y-values with a string identifying the series. + qubit: The qubit for which the analysis was done. + results: The results of the fit. Holds the fit function, xvals, and yvals. + kwargs: key word arguments supported are + - figsize: the size of the figure + - ax: the axis on which to plot the results """ - if len(self.x_values) > 1: - raise CalExpError('Default method does not support multi dimensional scan.') + import matplotlib + import matplotlib.pyplot as plt + from matplotlib.pyplot import cm + + if 'ax' in kwargs: + figure = kwargs['ax'].figure + else: + figure = plt.figure(figsize=kwargs.get('figsize', (6, 4))) + ax = figure.add_subplot(111) + + line_counts = 0 + for series_name, result in results.items(): + + # plot fit line + xval_interp = np.linspace(result.xvals[0], result.xvals[-1], 100) + yval_fit = self.fit_function(xval_interp, *result.fitvals) + + fit_line_color = cm.tab20.colors[(2*line_counts+1) % cm.tab20.N] + data_label = '{tag} (Q{qubit:d})'.format(tag=series_name, qubit=qubit) + ax.plot(xval_interp, yval_fit, '--', color=fit_line_color, label=data_label) + + # plot data scatter + data_scatter_color = cm.tab20.colors[(2*line_counts) % cm.tab20.N] + ax.plot(result.xvals, result.yvals, 'o', color=data_scatter_color) + + ax.set_xlim(result.xvals[0], result.xvals[-1]) + + line_counts += 1 + + ax.set_xlabel(kwargs.get('xlabel', kwargs.get('xlabel', 'Parameter')), fontsize=14) + ax.set_ylabel(kwargs.get('ylabel', kwargs.get('ylabel', 'Signal')), fontsize=14) + ax.grid() + ax.legend() - for series in data['series'].unique(): - xvals = np.array(data[data['series'] == series][self.x_values[0]]) - yvals = np.array(data[data['series'] == series]['value']) + if matplotlib.get_backend() in ['module://ipykernel.pylab.backend_inline', 'nbAgg']: + plt.close(figure) - yield xvals, yvals, series + return [figure] diff --git a/qiskit_experiments/calibration/analysis/fit_result.py b/qiskit_experiments/calibration/analysis/fit_result.py index e69de29bb2..fe6093e5d2 100644 --- a/qiskit_experiments/calibration/analysis/fit_result.py +++ b/qiskit_experiments/calibration/analysis/fit_result.py @@ -0,0 +1,43 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Results of a fit.""" + +from dataclasses import dataclass +import numpy as np + + +@dataclass(frozen=True) +class FitResult: + """ + A data class that store fitting results. + + fitvals and chisq are required while the other parameters are optional. + """ + + # fit parameters + fitvals: np.ndarray + + # chi squared value of fitting + chisq: float + + # standard deviation of parameters + stdevs: np.ndarray = np.array([]) + + # horizontal axis data values, may be used for visualization + xvals: np.ndarray = np.array([]) + + # vertical axis data values, may be used for visualization + yvals: np.ndarray = np.array([]) + + def __repr__(self) -> str: + return 'FitResult({})'.format(','.join(map(str, self.fitvals))) diff --git a/qiskit_experiments/calibration/analysis/utils.py b/qiskit_experiments/calibration/analysis/utils.py index e69de29bb2..ad2c60808d 100644 --- a/qiskit_experiments/calibration/analysis/utils.py +++ b/qiskit_experiments/calibration/analysis/utils.py @@ -0,0 +1,50 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Helper methods to extract data from the fits.""" + +import numpy as np +from typing import Type + +from .trigonometric import CosineFit +from .fit_result import FitResult + + +def get_period_fraction(analysis: Type, angle: float, fit_result: FitResult) -> float: + """ + Returns the x location corresponding to a given rotation angle. E.g. + if angle = pi and the function function is cos(2 pi a x) then return pi/2*pi*a. + Not all analysis routines will implement this. + + Args: + analysis: The analysis routing from which to retrieve a periodicity. + angle: The desired rotation angle. + fit_result: the result of the fit with the fit values. + """ + if issubclass(analysis, CosineFit): + return angle / (2 * np.pi * fit_result.fitvals[1]) + + raise NotImplementedError + +def get_min_location(analysis: Type, fit_result: FitResult) -> float: + """ + Returns the x location where the fit is minimum. + + Args: + analysis: The analysis routing from which to retrieve the minimum location. + fit_result: the result of the fit with the fit values. + """ + if isinstance(analysis, CosineFit): + fit_params = fit_result.fitvals + return (-np.pi - fit_params[2]) / (2*np.pi*fit_params[1]) + + raise NotImplementedError From a9f39d71c1ab6504c0df749f6ab0d73f83237313 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:41:44 +0100 Subject: [PATCH 05/32] * Added RoughAmplitude to experiments. --- .../calibration/experiments/__init__.py | 4 +- .../experiments/rough_amplitude.py | 106 +++++++++++++++++- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/qiskit_experiments/calibration/experiments/__init__.py b/qiskit_experiments/calibration/experiments/__init__.py index 0ecbce0962..f39c594c88 100644 --- a/qiskit_experiments/calibration/experiments/__init__.py +++ b/qiskit_experiments/calibration/experiments/__init__.py @@ -10,4 +10,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""All the experiments used to calibrate gates.""" \ No newline at end of file +"""All the experiments used to calibrate gates.""" + +from .rough_amplitude import RoughAmplitude diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 8736ff45ac..1408f6a414 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -12,18 +12,114 @@ """Rough amplitude calibration.""" +import numpy as np +from datetime import datetime + +from typing import Dict, List, Optional +from dataclasses import asdict + from qiskit_experiments.base_experiment import BaseExperiment +from qiskit_experiments.calibration.analysis import CosineFit, utils +from qiskit_experiments.calibration.metadata import CalibrationMetadata +from qiskit_experiments.calibration import CalibrationsDefinition +from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments.calibration import ParameterValue +from qiskit_experiments import ExperimentData +from qiskit.pulse import DriveChannel class RoughAmplitude(BaseExperiment): + """Rough amplitude calibration that scans the amplitude.""" # Analysis class for experiment - __analysis_class__ = None + __analysis_class__ = CosineFit - def __init__(self): + def __init__(self, + qubit: int, + cals: CalibrationsDefinition, + gate_name: str, + parameter_name: str, + data_processor: DataProcessor, + amplitudes = List[float], + group: Optional[str] = 'default'): + """ + Args: + qubit: The qubit on which to run the calibration. + cals: The instance to manage the calibrated pulse schedules and their + parameters. + gate_name: The name of the gate in cals to use when constructing the + rough amplitude calibration. + parameter_name: The name of the parameter to sweep, typically this will + be the amplitude of the pulse. + data_processor: The data processor used to process the measured data. + amplitudes: The amplitudes over which to sweep. + group: The calibration group that will be updated which defaults to 'default'. + """ + circuit_options = ['initial_layout'] + super().__init__([qubit], self.__class__.__name__, circuit_options) - qubits = None - super.__init__(qubits, 'rough_amplitude_calibration') + self._cal_def = cals + self.amplitudes = amplitudes + self.parameter = parameter_name + self._qubit = qubit + self._data_processor = data_processor + self._calibration_group = group + self._gate_name = gate_name def circuits(self, backend=None, **circuit_options): - """""" + """ + Create the circuits for a rough rabi amplitude calibration. + Args: + backend: Not used. + circuit_options: + + """ + circuits = [] + for amplitude in self.amplitudes: + meta = CalibrationMetadata( + experiment_type=self._type, + pulse_schedule_name=self.__class__.__name__, + x_values=amplitude, + qubits=self._qubit + ) + + template_qc = self._cal_def.get_circuit(self._gate_name, (self._qubit,), [self.parameter]) + template_qc.measure(0, 0) + template_qc.name = 'circuit' + + qc = template_qc.assign_parameters({template_qc.parameters[0]: amplitude}) + qc.metadata = asdict(meta) + circuits.append(qc) + + return circuits + + def update_calibrations(self, experiment_data: ExperimentData, + update_pulses: Optional[Dict[str, float]] = None, index: int = -1): + """ + Updates the amplitude of the pulses. This will preserve the existing phase. + + Args: + experiment_data: The experiment data to use to update the pulse amplitudes. + update_pulses: The pulse to update. The key is the name of the pulse parameter and + the value is the angle to extract from the cosine fit. For example, {'amp_xp': + np.pi, 'amp_x90p': np.pi/2} will update the amplitude ot the xp and x90p pulses. + index: The index of analysis result to use in experiment_data. If this is not + specified then the latest added analysis result is used. + """ + if update_pulses is None: + update_pulses = {self._gate_name: np.pi/2} + + fit_result = experiment_data.analysis_result(index)['default'] + + for param_name, angle in update_pulses.items(): + amp = self._cal_def.parameter_value(param_name, + DriveChannel(self._qubit), + group=self._calibration_group) + + phase = np.exp(1.0j*np.angle(amp)) + value = phase*utils.get_period_fraction(self.__analysis_class__, angle, fit_result) + + param_val = ParameterValue(value, datetime.now(), exp_id=experiment_data.experiment_id, + group=self._calibration_group) + + self._cal_def.add_parameter_value(param_name, param_val, DriveChannel(self._qubit)) From 669da4df2676ed08f84e5e74af7175ecca39cc6e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:42:18 +0100 Subject: [PATCH 06/32] * Removed to dict method from CalibrationMetadata. * Added experiment_type to CalibrationMetadata. --- qiskit_experiments/calibration/metadata.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/qiskit_experiments/calibration/metadata.py b/qiskit_experiments/calibration/metadata.py index 2f4d6f7476..fc0e94b25b 100644 --- a/qiskit_experiments/calibration/metadata.py +++ b/qiskit_experiments/calibration/metadata.py @@ -28,6 +28,9 @@ class CalibrationMetadata: # The name of the calibration experiment. name: str = None + # Type of the calibration experiment + experiment_type: str = None + # Name of the pulse schedule that was used in the calibration experiment. pulse_schedule_name: str = None @@ -44,19 +47,3 @@ class CalibrationMetadata: # Physical qubits used. qubits: List[int] = None - - # Mapping of qubit index and classical register index. - # The key is the qubit index and value is the classical bit index. - # This mapping is automatically generated at BaseCalibrationExperiment class. - register_map: Dict[int, int] = None - - def __post_init__(self): - """Ensure that keys are integers. This is needed because in JSon keys are str.""" - if not self.register_map: - return - - register_map = {} - for key, value in self.register_map.items(): - register_map[int(key)] = int(value) - - self.register_map = register_map From 7e3db7e694dbd3d8540abbebb509ff560524dfaf Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:43:30 +0100 Subject: [PATCH 07/32] * Added figures to ExperimentData. --- qiskit_experiments/experiment_data.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/qiskit_experiments/experiment_data.py b/qiskit_experiments/experiment_data.py index b7cbda8394..44bde1c71c 100644 --- a/qiskit_experiments/experiment_data.py +++ b/qiskit_experiments/experiment_data.py @@ -41,6 +41,9 @@ def __init__(self, experiment): # Experiment Data self._data = [] + # Figures + self._figures = [] + # Analysis self._analysis_results = [] @@ -94,6 +97,15 @@ def data(self): """Return stored experiment data""" return self._data + @property + def figures(self): + """Return the figures.""" + return self._figures + + def add_figure(self, figure): + """Add a figure to the experiment data.""" + self._figures.append(figure) + def add_data(self, data): """Add data to the experiment. From 15880407ab9dc4ccf650d6e4583ece1ab79ecdf6 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 18 Mar 2021 20:44:20 +0100 Subject: [PATCH 08/32] * Added tests. --- test/calibration/test_analysis.py | 14 ++ .../test_calibrations_definition.py | 13 ++ test/calibration/test_data_processing.py | 149 ++++++++++++++++-- test/calibration/test_experiments.py | 62 +++++++- 4 files changed, 226 insertions(+), 12 deletions(-) diff --git a/test/calibration/test_analysis.py b/test/calibration/test_analysis.py index e69de29bb2..b3d95fa32a 100644 --- a/test/calibration/test_analysis.py +++ b/test/calibration/test_analysis.py @@ -0,0 +1,14 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test calibration analysis routines.""" + diff --git a/test/calibration/test_calibrations_definition.py b/test/calibration/test_calibrations_definition.py index e69de29bb2..fce351056e 100644 --- a/test/calibration/test_calibrations_definition.py +++ b/test/calibration/test_calibrations_definition.py @@ -0,0 +1,13 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the class that holds the calibrations.""" diff --git a/test/calibration/test_data_processing.py b/test/calibration/test_data_processing.py index 2340d0ae19..32ca8f7b97 100644 --- a/test/calibration/test_data_processing.py +++ b/test/calibration/test_data_processing.py @@ -12,39 +12,84 @@ """Data processor tests.""" +from qiskit_experiments import ExperimentData +from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.calibration import DataProcessor -from qiskit_experiments.calibration.metadata import CalibrationMetadata -from qiskit_experiments.calibration.data_processing import SystemKernel, SystemDiscriminator -from qiskit.result import models +from qiskit_experiments.calibration.data_processing import (SystemKernel, SystemDiscriminator, + ToReal, ToImag, Population) +from qiskit.result.models import ExperimentResultData, ExperimentResult from qiskit.result import Result from qiskit.test import QiskitTestCase from qiskit.qobj.utils import MeasLevel +from qiskit.qobj.common import QobjExperimentHeader + + +class FakeExperiment(BaseExperiment): + """Fake experiment class for testing.""" + + def __init__(self): + """Initialise the fake experiment.""" + self._type = None + super().__init__((0, ), 'fake_test_experiment') + + def circuits(self, backend=None, **circuit_options): + """Fake circuits.""" + return [] class DataProcessorTest(QiskitTestCase): """Class to test DataProcessor.""" def setUp(self): + """Setup variables used for testing.""" self.base_result_args = dict(backend_name='test_backend', backend_version='1.0.0', qobj_id='id-123', job_id='job-123', success=True) + mem1 = ExperimentResultData(memory=[[[1103260.0, -11378508.0], [2959012.0, -16488753.0]], + [[442170.0, -19283206.0], [-5279410.0, -15339630.0]], + [[3016514.0, -14548009.0], [-3404756.0, -16743348.0]]]) + + mem2 = ExperimentResultData(memory=[[[5131962.0, -16630257.0], [4438870.0, -13752518.0]], + [[3415985.0, -16031913.0], [2942458.0, -15840465.0]], + [[5199964.0, -14955998.0], [4030843.0, -14538923.0]]]) + + header1 = QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1]], + creg_sizes=[['meas', 2]], global_phase=0.0, memory_slots=2, + metadata={'experiment_type': 'fake_test_experiment', + 'x_values': 0.0}) + + header2 = QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1]], + creg_sizes=[['meas', 2]], global_phase=0.0, memory_slots=2, + metadata={'experiment_type': 'fake_test_experiment', + 'x_values': 1.0}) + + res1 = ExperimentResult(shots=3, success=True, meas_level=1, data=mem1, header=header1) + res2 = ExperimentResult(shots=3, success=True, meas_level=1, data=mem2, header=header2) + + self.result_lvl1 = Result(results=[res1, res2], **self.base_result_args) + super().setUp() def test_empty_processor(self): """Check that a DataProcessor without steps does nothing.""" - raw_counts = {'0x0': 4, '0x2': 10} - data = models.ExperimentResultData(counts=dict(**raw_counts)) - exp_result = models.ExperimentResult(shots=14, success=True, meas_level=2, data=data) - result = Result(results=[exp_result], **self.base_result_args) + raw_counts = {'0x0': 4, '0x2': 5} + data = ExperimentResultData(counts=dict(**raw_counts)) + header = QobjExperimentHeader(metadata={'experiment_type': 'fake_test_experiment'}) + result_ = ExperimentResult(shots=9, success=True, meas_level=2, data=data, header=header) + + result = Result(results=[result_], **self.base_result_args) + + exp_data = ExperimentData(FakeExperiment()) + exp_data.add_data(result) data_processor = DataProcessor() - processed = data_processor.format_data(result, metadata=CalibrationMetadata(), index=0) - self.assertEqual(processed.get_counts(0)['0'], 4) - self.assertEqual(processed.get_counts(0)['10'], 10) + data_processor.format_data(exp_data.data) + self.assertEqual(exp_data.data[0]['counts']['0'], 4) + self.assertEqual(exp_data.data[0]['counts']['10'], 5) def test_append_kernel(self): """Tests that we can add a kernel and a discriminator.""" @@ -56,3 +101,87 @@ def test_append_kernel(self): processor.append(SystemDiscriminator(None)) self.assertEqual(processor.meas_level(), MeasLevel.CLASSIFIED) + + def test_output_key(self): + """Test that we can properly get the output key from the node.""" + processor = DataProcessor() + self.assertEqual(processor.output_key(), 'counts') + + processor.append(SystemKernel()) + self.assertEqual(processor.output_key(), 'memory') + + processor.append(ToReal()) + self.assertEqual(processor.output_key(), 'memory') + + processor = DataProcessor() + processor.append(SystemKernel()) + processor.append(SystemDiscriminator(None)) + self.assertEqual(processor.output_key(), 'counts') + + processor = DataProcessor() + processor.append(Population()) + self.assertEqual(processor.output_key(), 'populations') + + def test_to_real(self): + """Test scaling and conversion to real part.""" + processor = DataProcessor() + processor.append(ToReal(scale=1e-3)) + self.assertEqual(processor.output_key(), 'memory') + + exp_data = ExperimentData(FakeExperiment()) + exp_data.add_data(self.result_lvl1) + + processor.format_data(exp_data.data[0]) + + expected = {'memory': [[1103.26, 2959.012], [442.17, -5279.41], [3016.514, -3404.7560]], + 'metadata': {'experiment_type': 'fake_test_experiment', 'x_values': 0.0}} + + self.assertEqual(exp_data.data[0], expected) + + # Test that we can average single-shots + processor = DataProcessor() + processor.append(ToReal(scale=1e-3, average=True)) + self.assertEqual(processor.output_key(), 'memory') + + exp_data = ExperimentData(FakeExperiment()) + exp_data.add_data(self.result_lvl1) + + processor.format_data(exp_data.data[0]) + + expected = {'memory': [1520.6480000000001, -1908.3846666666666], + 'metadata': {'experiment_type': 'fake_test_experiment', 'x_values': 0.0}} + + self.assertEqual(exp_data.data[0], expected) + + def test_to_imag(self): + """Test that we can average the data.""" + processor = DataProcessor() + processor.append(ToImag(scale=1e-3)) + self.assertEqual(processor.output_key(), 'memory') + + exp_data = ExperimentData(FakeExperiment()) + exp_data.add_data(self.result_lvl1) + + processor.format_data(exp_data.data[0]) + + expected = {'memory': [[-11378.508, -16488.753], + [-19283.206000000002, -15339.630000000001], + [-14548.009, -16743.348]], + 'metadata': {'experiment_type': 'fake_test_experiment', 'x_values': 0.0}} + + self.assertEqual(exp_data.data[0], expected) + + # Test that we can average single-shots + processor = DataProcessor() + processor.append(ToImag(scale=1e-3, average=True)) + self.assertEqual(processor.output_key(), 'memory') + + exp_data = ExperimentData(FakeExperiment()) + exp_data.add_data(self.result_lvl1) + + processor.format_data(exp_data.data[0]) + + expected = {'memory': [-15069.907666666666, -16190.577], + 'metadata': {'experiment_type': 'fake_test_experiment', 'x_values': 0.0}} + + self.assertEqual(exp_data.data[0], expected) diff --git a/test/calibration/test_experiments.py b/test/calibration/test_experiments.py index bc5b39ccf7..f0ad39b284 100644 --- a/test/calibration/test_experiments.py +++ b/test/calibration/test_experiments.py @@ -12,6 +12,64 @@ """Test the rough amplitude experiment.""" -class TestRoughAmplitude: +from qiskit_experiments.calibration import CalibrationsDefinition +from qiskit_experiments.calibration import ParameterValue +from qiskit_experiments.calibration import RoughAmplitude +import qiskit.pulse as pulse +from qiskit.test import QiskitTestCase +from qiskit.pulse import Drag, DriveChannel +from qiskit.test.mock import FakeAlmaden +from qiskit.circuit import Parameter, Gate - \ No newline at end of file + +class TestCalibrationExperiments(QiskitTestCase): + + """Class to test calibration experiments.""" + def setUp(self): + """Setup variables used for testing.""" + self.backend = FakeAlmaden() + self.cals = CalibrationsDefinition(self.backend) + + sq_freq = Parameter('freq') + sigma_xp = Parameter('σ_xp') + d0 = Parameter('0') + amp_xp = Parameter('amp_xp') + amp_x90p = Parameter('amp_x90p') + amp_y90p = Parameter('amp_y90p') + beta_xp = Parameter('β_xp') + + with pulse.build(name='xp') as xp: + pulse.play(Drag(160, amp_xp, sigma_xp, beta_xp), DriveChannel(d0)) + + with pulse.build(name='xm') as xm: + pulse.play(Drag(160, -amp_xp, sigma_xp, beta_xp), DriveChannel(d0)) + + with pulse.build(name='x90p') as x90p: + pulse.play(Drag(160, amp_x90p, sigma_xp, beta_xp), DriveChannel(d0)) + + with pulse.build(name='y90p') as y90p: + pulse.play(Drag(160, amp_y90p, sigma_xp, beta_xp), DriveChannel(d0)) + + self.cals.add_schedules([xp, x90p, y90p, xm]) + + self.cals.add_parameter_value('σ_xp', ParameterValue(40), ch_type=DriveChannel) + self.cals.add_parameter_value('amp_xp', ParameterValue(0.2), ch_type=DriveChannel) + self.cals.add_parameter_value('amp_x90p', ParameterValue(0.1), ch_type=DriveChannel) + self.cals.add_parameter_value('amp_y90p', ParameterValue(0.1j), ch_type=DriveChannel) + self.cals.add_parameter_value('β_xp', ParameterValue(0.0), ch_type=DriveChannel) + + def test_rough_amplitude(self): + """The the rough amplitude calibration.""" + + qubit = 3 + amps = [-0.5, 0.5] + amp = RoughAmplitude(qubit, self.cals, 'xp', 'amp_xp', amps) + circs = amp.transpiled_circuits(self.backend) + + # Check that there is a gate on qubit 3. + self.assertEqual(circs[1].data[0][1][0].index, qubit) + self.assertTrue(isinstance(circs[1].data[0][0], Gate)) + + # Check that we have calibrations for the xp gate on qubit 3 + for idx, amp in enumerate(amps): + self.assertTrue(((qubit, ), (amp, )) in circs[idx].calibrations['xp']) From 96e64071b2dd4f6123112bfcbeb3b652f7ca03e6 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 09:05:29 +0100 Subject: [PATCH 09/32] * Added pandas to requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index fb0a5a9e9a..22c28315f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ numpy>=1.17 qiskit-terra>=0.16.0 +pandas From 8eb15b312b7ba2f2d644c797862a89ec3a5228f1 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 09:28:36 +0100 Subject: [PATCH 10/32] * Added dataclasses to requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 22c28315f7..f00ea4d673 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ numpy>=1.17 qiskit-terra>=0.16.0 pandas +dataclasses From 6dc002a1dd518212e07f7babae8f25e401aba70b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 10:54:56 +0100 Subject: [PATCH 11/32] * Bumped version of terra to 0.17.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f00ea4d673..b11a5dc78a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ numpy>=1.17 -qiskit-terra>=0.16.0 +qiskit-terra>=0.17.0 pandas dataclasses From b24b1235658a8d5f5816b6d1d73fac8601c9b15c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 11:09:03 +0100 Subject: [PATCH 12/32] * Added HAS_MATPLOTLIB to the calibration analysis class. --- .../analysis/calibration_analysis.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index e4b2832df3..7bb9553d7a 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -26,6 +26,14 @@ from qiskit_experiments.experiment_data import AnalysisResult from scipy import optimize +try: + import matplotlib + from matplotlib import pyplot as plt + + HAS_MATPLOTLIB = True +except ImportError: + HAS_MATPLOTLIB = False + class BaseCalibrationAnalysis(BaseAnalysis): """Abstract base class for all calibration analysis classes.""" @@ -39,9 +47,9 @@ def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.nda yvals: y values to fit. Yield: - Set of initial guess for parameters. - If multiple guesses are returned fit is performed for all parameter set. - Error is measured by Chi squared value and the best fit result is chosen. + A set of initial parameter guesses. + If multiple guesses are returned a fit is performed for each guess. + The error of the fit is measured by the Chi squared and the best fit result is chosen. Note: This should return values with yield rather than return. @@ -49,7 +57,7 @@ def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.nda @abstractmethod def fit_boundary(self, xvals: np.ndarray, yvals: np.ndarray) -> List[Tuple[float, float]]: - """Returns boundary of parameters to fit. + """Returns the boundary of the parameters to fit. Args: xvals: x values to fit. @@ -62,18 +70,19 @@ def fit_function(self, xvals: np.ndarray, *args) -> np.ndarray: Args: xvals: x values to fit. + args: The parameters of the fit function. """ def chi_squared(self, parameters: np.ndarray, xvals: np.ndarray, - yvals: np.ndarray): - """Calculate reduced Chi squared value. + yvals: np.ndarray) -> float: + """Calculate the reduced Chi squared value. Args: - parameters: Parameters for fit function. - xvals: X values to fit. - yvals: Y values to fit. + parameters: Parameters for the fit function. + xvals: x values to fit. + yvals: y values to fit. """ fit_yvals = self.fit_function(xvals, *parameters) @@ -85,13 +94,16 @@ def chi_squared(self, def _run_analysis(self, experiment_data: ExperimentData, qubit=0, data_processor=DataProcessor(), plot=False, **kwargs) -> any: """ - Analyze the given experiment data. Notes: data is a List of Dict. Args: experiment_data: The data to analyse. + qubit: The qubit for which to analyse the data. + data_processor: The data processor which transforms the measured data to + a format that can be analyzed. For example, IQ data may be converted to + a signal by taking the real-part. Returns: any: the output of the analysis, @@ -120,7 +132,7 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, for xvals, yvals, series in series.series(): best_result = None for initial_guess in self.initial_guess(xvals, yvals): - # fit for each initial guess if there are many starting point + fit_result = optimize.minimize( fun=self.chi_squared, x0=initial_guess, @@ -140,20 +152,19 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, # fit failed, output log `fit_result.message` pass - # keep the best result results[series] = best_result experiment_data.add_analysis_result(results) figures = None - if plot: + if plot and HAS_MATPLOTLIB: figures = self._plot(qubit, results, **kwargs) return results, figures def _plot(self, qubit, results, **kwargs): """ - Make plots of the results. + Plot the result. Args: qubit: The qubit for which the analysis was done. @@ -162,10 +173,6 @@ def _plot(self, qubit, results, **kwargs): - figsize: the size of the figure - ax: the axis on which to plot the results """ - import matplotlib - import matplotlib.pyplot as plt - from matplotlib.pyplot import cm - if 'ax' in kwargs: figure = kwargs['ax'].figure else: @@ -179,12 +186,12 @@ def _plot(self, qubit, results, **kwargs): xval_interp = np.linspace(result.xvals[0], result.xvals[-1], 100) yval_fit = self.fit_function(xval_interp, *result.fitvals) - fit_line_color = cm.tab20.colors[(2*line_counts+1) % cm.tab20.N] + fit_line_color = plt.cm.tab20.colors[(2*line_counts+1) % plt.cm.tab20.N] data_label = '{tag} (Q{qubit:d})'.format(tag=series_name, qubit=qubit) ax.plot(xval_interp, yval_fit, '--', color=fit_line_color, label=data_label) # plot data scatter - data_scatter_color = cm.tab20.colors[(2*line_counts) % cm.tab20.N] + data_scatter_color = plt.cm.tab20.colors[(2*line_counts) % plt.cm.tab20.N] ax.plot(result.xvals, result.yvals, 'o', color=data_scatter_color) ax.set_xlim(result.xvals[0], result.xvals[-1]) From bf826f3a480d9b002ba25fe7fb58dbe19b51617a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 11:30:25 +0100 Subject: [PATCH 13/32] * Added test for initial guess of frequency. --- .../calibration/analysis/trigonometric.py | 4 +-- test/calibration/test_fitting.py | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 test/calibration/test_fitting.py diff --git a/qiskit_experiments/calibration/analysis/trigonometric.py b/qiskit_experiments/calibration/analysis/trigonometric.py index ba22be9f15..a99f0be633 100644 --- a/qiskit_experiments/calibration/analysis/trigonometric.py +++ b/qiskit_experiments/calibration/analysis/trigonometric.py @@ -19,7 +19,7 @@ from .calibration_analysis import BaseCalibrationAnalysis -def _freq_guess(xvals: np.ndarray, yvals: np.ndarray): +def freq_guess(xvals: np.ndarray, yvals: np.ndarray): """Initial frequency guess for oscillating data. Args: @@ -58,7 +58,7 @@ def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.nda """ y_mean = np.mean(yvals) a0 = np.max(np.abs(yvals)) - np.abs(y_mean) - f0 = max(0, _freq_guess(xvals, yvals)) + f0 = max(0, freq_guess(xvals, yvals)) for phi in np.linspace(-np.pi, np.pi, 10): yield np.array([a0, f0, phi, y_mean]) diff --git a/test/calibration/test_fitting.py b/test/calibration/test_fitting.py new file mode 100644 index 0000000000..de7a36cb9e --- /dev/null +++ b/test/calibration/test_fitting.py @@ -0,0 +1,32 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Package to test fitting in calibration module.""" + +import numpy as np + +from qiskit_experiments.calibration.analysis.trigonometric import freq_guess +from qiskit.test import QiskitTestCase + + +class TestCalibrationFitting(QiskitTestCase): + """Class to test the functionality of fitters.""" + + def test_frequency_guess(self): + """Test the initial frequency estimation function.""" + + xvals = np.linspace(0, 1, 200) + yvals = np.cos(2*np.pi*xvals) + 1.1 + + freq = freq_guess(xvals, yvals) + + self.assertAlmostEqual(freq, 1.0, places=2) From f12e3a4688365a62642d912c70c37ddc5fe3516c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 11:55:38 +0100 Subject: [PATCH 14/32] * Lint fixes in analysis. --- .../calibration/analysis/__init__.py | 2 ++ .../analysis/calibration_analysis.py | 24 ++++++++++++------- .../calibration/analysis/trigonometric.py | 14 +++++++++-- .../calibration/analysis/utils.py | 21 ++++++++++++---- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/qiskit_experiments/calibration/analysis/__init__.py b/qiskit_experiments/calibration/analysis/__init__.py index 6044666f74..6884bb768b 100644 --- a/qiskit_experiments/calibration/analysis/__init__.py +++ b/qiskit_experiments/calibration/analysis/__init__.py @@ -10,4 +10,6 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Qiskit experiments calibration analysis roots.""" + from .trigonometric import CosineFit diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 7bb9553d7a..296008d5d5 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -12,19 +12,20 @@ """Base analysis class for calibrations.""" -import numpy as np from abc import abstractmethod from typing import Iterator, List, Tuple +import numpy as np +from scipy import optimize -from .fit_result import FitResult +from qiskit.qobj.utils import MeasReturnType +from qiskit_experiments.experiment_data import AnalysisResult from qiskit_experiments.base_analysis import BaseAnalysis from qiskit_experiments.calibration import DataProcessor from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments import ExperimentData from qiskit_experiments.calibration.data_processing.processed_data import ProcessedData -from qiskit.qobj.utils import MeasReturnType -from qiskit_experiments.experiment_data import AnalysisResult -from scipy import optimize +from .fit_result import FitResult + try: import matplotlib @@ -83,6 +84,9 @@ def chi_squared(self, parameters: Parameters for the fit function. xvals: x values to fit. yvals: y values to fit. + + Returns: + The chi-squared value. """ fit_yvals = self.fit_function(xvals, *parameters) @@ -91,6 +95,7 @@ def chi_squared(self, return chi_sq / dof + #pylint: disable = arguments-differ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, data_processor=DataProcessor(), plot=False, **kwargs) -> any: """ @@ -110,14 +115,14 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, """ # 1) Format the data using the DataProcessor for the analysis. - for exp_idx, data in enumerate(experiment_data.data): + for data in experiment_data.data: data_processor.format_data(data) # 2) Extract series information from the data key = data_processor.output_key() meas_return = data_processor.meas_return() series = ProcessedData() - for exp_idx, data in enumerate(experiment_data.data): + for data in experiment_data.data: metadata = CalibrationMetadata(**data['metadata']) if meas_return == MeasReturnType.AVERAGE: @@ -162,7 +167,7 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, return results, figures - def _plot(self, qubit, results, **kwargs): + def _plot(self, qubit, results, **kwargs) -> List: """ Plot the result. @@ -172,6 +177,9 @@ def _plot(self, qubit, results, **kwargs): kwargs: key word arguments supported are - figsize: the size of the figure - ax: the axis on which to plot the results + + Returns: + A matplotlib figure. """ if 'ax' in kwargs: figure = kwargs['ax'].figure diff --git a/qiskit_experiments/calibration/analysis/trigonometric.py b/qiskit_experiments/calibration/analysis/trigonometric.py index a99f0be633..aab44b4e3e 100644 --- a/qiskit_experiments/calibration/analysis/trigonometric.py +++ b/qiskit_experiments/calibration/analysis/trigonometric.py @@ -12,19 +12,22 @@ """Trigonometric analysis.""" +from typing import Iterator, List, Tuple import numpy as np from scipy import signal -from typing import Iterator, List, Tuple from .calibration_analysis import BaseCalibrationAnalysis -def freq_guess(xvals: np.ndarray, yvals: np.ndarray): +def freq_guess(xvals: np.ndarray, yvals: np.ndarray) -> float: """Initial frequency guess for oscillating data. Args: xvals: The independent values. yvals: The dependent values. + + Returns: + frequency: An estimation of the frequency based on a FFT. """ # Subtract DC component @@ -55,6 +58,9 @@ def initial_guess(self, xvals: np.ndarray, yvals: np.ndarray) -> Iterator[np.nda Args: xvals: The independent values. yvals: The dependent values. + + Yields: + guess: An array containing the values of the initial guess. """ y_mean = np.mean(yvals) a0 = np.max(np.abs(yvals)) - np.abs(y_mean) @@ -68,6 +74,10 @@ def fit_function(self, xvals: np.ndarray, *args) -> np.ndarray: Args: xvals: The values along the x-axis. + args: The parameters of the fit: as [a, f, phi, b], see class docstring. + + Returns: + The value of the function with the given x value and parameters. """ return args[0] * np.cos(2 * np.pi * args[1] * xvals + args[2]) + args[3] diff --git a/qiskit_experiments/calibration/analysis/utils.py b/qiskit_experiments/calibration/analysis/utils.py index ad2c60808d..4ec7a75a2d 100644 --- a/qiskit_experiments/calibration/analysis/utils.py +++ b/qiskit_experiments/calibration/analysis/utils.py @@ -12,9 +12,10 @@ """Helper methods to extract data from the fits.""" -import numpy as np from typing import Type +import numpy as np +from qiskit_experiments.calibration.exceptions import CalibrationError from .trigonometric import CosineFit from .fit_result import FitResult @@ -29,22 +30,32 @@ def get_period_fraction(analysis: Type, angle: float, fit_result: FitResult) -> analysis: The analysis routing from which to retrieve a periodicity. angle: The desired rotation angle. fit_result: the result of the fit with the fit values. + + Returns: + period fraction: The x location corresponding to a given rotation angle. + + Raises: + CalibrationError: if the fit function is not recognized. """ if issubclass(analysis, CosineFit): return angle / (2 * np.pi * fit_result.fitvals[1]) - raise NotImplementedError + raise CalibrationError(f'Analysis class {analysis} is not supported.') def get_min_location(analysis: Type, fit_result: FitResult) -> float: """ - Returns the x location where the fit is minimum. - Args: analysis: The analysis routing from which to retrieve the minimum location. fit_result: the result of the fit with the fit values. + + Returns: + The location where the fit is minimum. + + Raises: + CalibrationError: if the fit function is not recognized. """ if isinstance(analysis, CosineFit): fit_params = fit_result.fitvals return (-np.pi - fit_params[2]) / (2*np.pi*fit_params[1]) - raise NotImplementedError + raise CalibrationError(f'Analysis class {analysis} is not supported.') From 190fd59d8fccdd4ae4d8c0803d2d52834d758249 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 12:20:00 +0100 Subject: [PATCH 15/32] * Lint fix on data processing. --- .../analysis/calibration_analysis.py | 10 ++-- .../calibration/data_processing/__init__.py | 2 + .../calibration/data_processing/base.py | 9 +--- .../data_processing/data_processor.py | 26 ++------- .../calibration/data_processing/nodes.py | 53 +++++++++---------- .../data_processing/processed_data.py | 9 ++-- 6 files changed, 44 insertions(+), 65 deletions(-) diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 296008d5d5..ea521b87b9 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -120,16 +120,18 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, # 2) Extract series information from the data key = data_processor.output_key() - meas_return = data_processor.meas_return() series = ProcessedData() for data in experiment_data.data: metadata = CalibrationMetadata(**data['metadata']) - if meas_return == MeasReturnType.AVERAGE: - yval = data[key][qubit] - else: + # Single-shot data. + if isinstance(data[key][0], list): yval = [data[key][_][qubit] for _ in range(len(data[key]))] + # Averaged data. + else: + yval = data[key][qubit] + series.add_data_point(metadata.x_values, yval, metadata.series) # 3) Fit the data for each initial guess and series. diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/calibration/data_processing/__init__.py index fed8744117..800bde672c 100644 --- a/qiskit_experiments/calibration/data_processing/__init__.py +++ b/qiskit_experiments/calibration/data_processing/__init__.py @@ -10,6 +10,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Qiskit experiments calibration data processing roots.""" + from .base import DataAction from .nodes import SystemKernel from .nodes import SystemDiscriminator diff --git a/qiskit_experiments/calibration/data_processing/base.py b/qiskit_experiments/calibration/data_processing/base.py index dab31fac76..0838d6511e 100644 --- a/qiskit_experiments/calibration/data_processing/base.py +++ b/qiskit_experiments/calibration/data_processing/base.py @@ -69,7 +69,7 @@ def process(self, data: Dict[str, Any]): data: the data to which the data processing step will be applied. """ - def format_data(self, data: Dict[str, Any]) -> Any: + def format_data(self, data: Dict[str, Any]): """ Apply the data action of this node and call the child node's format_data method. @@ -77,16 +77,11 @@ def format_data(self, data: Dict[str, Any]) -> Any: data: A dict containing the data. The action nodes in the data processor will raise errors if the data does not contain the appropriate data. - - Returns: - processed_data: """ processed_data = self.process(data) if self._child: - return self._child.format_data(processed_data) - else: - return processed_data + self._child.format_data(processed_data) class NodeType(Enum): diff --git a/qiskit_experiments/calibration/data_processing/data_processor.py b/qiskit_experiments/calibration/data_processing/data_processor.py index 6c686324a6..cccbc87ee8 100644 --- a/qiskit_experiments/calibration/data_processing/data_processor.py +++ b/qiskit_experiments/calibration/data_processing/data_processor.py @@ -14,9 +14,9 @@ from typing import Any, Dict, Union +from qiskit.qobj.utils import MeasLevel from .nodes import SystemKernel, SystemDiscriminator from .base import NodeType, DataAction -from qiskit.qobj.utils import MeasLevel, MeasReturnType class DataProcessor: @@ -25,15 +25,12 @@ class DataProcessor: by the calibration analysis classes. """ - def __init__(self, average: bool = True): + def __init__(self): """Create an empty chain of data ProcessingSteps. - TODO self._average is not used. - Args: average: Set `True` to average outcomes. """ - self._average = average self._root_node = None def append(self, node: DataAction): @@ -48,18 +45,6 @@ def append(self, node: DataAction): else: self._root_node = node - def meas_return(self) -> MeasReturnType: - """ - Returns: - MeasReturnType: The appropriate measurement format to use this analysis chain. - """ - if DataProcessor.check_discriminator(self._root_node): - # if discriminator is defined, return type should be single. - # quantum state cannot be discriminated with averaged IQ coordinate. - return MeasReturnType.SINGLE - - return MeasReturnType.AVERAGE if self._average else MeasReturnType.SINGLE - def meas_level(self) -> MeasLevel: """ Returns: @@ -97,7 +82,6 @@ def output_key(self) -> str: return 'counts' - def format_data(self, data: Dict[str, Any]): """ Format Qiskit result data. @@ -110,10 +94,8 @@ def format_data(self, data: Dict[str, Any]): data: The data, typically from an ExperimentData instance, that needs to be processed. This dict also contains the metadata of each experiment. """ - if not self._root_node: - return data - - return self._root_node.format_data(data) + if self._root_node: + self._root_node.format_data(data) @classmethod def check_kernel(cls, node: DataAction) -> Union[None, DataAction]: diff --git a/qiskit_experiments/calibration/data_processing/nodes.py b/qiskit_experiments/calibration/data_processing/nodes.py index 010b5ff4e2..22e9c7beec 100644 --- a/qiskit_experiments/calibration/data_processing/nodes.py +++ b/qiskit_experiments/calibration/data_processing/nodes.py @@ -12,16 +12,15 @@ """Different data analysis steps.""" -from typing import Any, Dict, List, Optional, Union - +from typing import Any, Dict, Optional import numpy as np -from . import base -from . import DataAction + from qiskit_experiments.calibration.exceptions import CalibrationError +from .base import DataAction, iq_data, kernel, discriminator, prev_node, population -@base.kernel -@base.prev_node() +@kernel +@prev_node() class SystemKernel(DataAction): """Backend system kernel.""" @@ -29,29 +28,25 @@ def __init__(self, name: Optional[str] = None): self.name = name super().__init__() - def process(self, data: Any, **kwargs) -> Union[float, np.ndarray]: + def process(self, data: Dict[str, Any]): """ Args: - data: - - Returns: - data: The data after applying the integration kernel. + data: The data dictionary to process. """ - return data -@base.discriminator -@base.prev_node(SystemKernel) +@discriminator +@prev_node(SystemKernel) class SystemDiscriminator(DataAction): """Backend system discriminator.""" - def __init__(self, discriminator, name: Optional[str] = None): + def __init__(self, discriminator_, name: Optional[str] = None): """ Args: - discriminator: The discriminator used to transform the data to counts. + discriminator_: The discriminator used to transform the data to counts. For example, transform IQ data to counts. """ - self.discriminator = discriminator + self.discriminator = discriminator_ self.name = name super().__init__() @@ -72,8 +67,8 @@ def process(self, data: Dict[str, Any]): data['counts'] = self.discriminator.discriminate(np.array(data['memory'])) -@base.iq_data -@base.prev_node(SystemKernel) +@iq_data +@prev_node(SystemKernel) class ToReal(DataAction): """IQ data post-processing. This returns real part of IQ data.""" @@ -87,7 +82,7 @@ def __init__(self, scale: Optional[float] = 1.0, average: bool = False): self.average = average super().__init__() - def process(self, data: Dict[str, Any], **kwargs): + def process(self, data: Dict[str, Any]): """ Modifies the data inplace by taking the real part of the memory and scaling it by the given factor. @@ -103,9 +98,9 @@ def process(self, data: Dict[str, Any], **kwargs): f'Cannot apply {self.__class__.__name__}') # Single shot data - if isinstance(data['memory'][0][0], List): + if isinstance(data['memory'][0][0], list): new_mem = [] - for shot_idx, shot in enumerate(data['memory']): + for shot in data['memory']: new_mem.append([self.scale*_[0] for _ in shot]) if self.average: @@ -117,8 +112,8 @@ def process(self, data: Dict[str, Any], **kwargs): data['memory'] = new_mem -@base.iq_data -@base.prev_node(SystemKernel) +@iq_data +@prev_node(SystemKernel) class ToImag(DataAction): """IQ data post-processing. This returns imaginary part of IQ data.""" @@ -131,7 +126,7 @@ def __init__(self, scale: Optional[float] = 1.0, average: bool = False): self.average = average super().__init__() - def process(self, data: Dict[str, Any], **kwargs): + def process(self, data: Dict[str, Any]): """ Scales the imaginary part of IQ data. @@ -146,9 +141,9 @@ def process(self, data: Dict[str, Any], **kwargs): f'Cannot apply {self.__class__.__name__}') # Single shot data - if isinstance(data['memory'][0][0], List): + if isinstance(data['memory'][0][0], list): new_mem = [] - for shot_idx, shot in enumerate(data['memory']): + for shot in data['memory']: new_mem.append([self.scale*_[1] for _ in shot]) if self.average: @@ -161,8 +156,8 @@ def process(self, data: Dict[str, Any], **kwargs): data['memory'] = new_mem -@base.population -@base.prev_node(SystemDiscriminator) +@population +@prev_node(SystemDiscriminator) class Population(DataAction): """Count data post processing. This returns population.""" diff --git a/qiskit_experiments/calibration/data_processing/processed_data.py b/qiskit_experiments/calibration/data_processing/processed_data.py index eccea97214..dabdf0126f 100644 --- a/qiskit_experiments/calibration/data_processing/processed_data.py +++ b/qiskit_experiments/calibration/data_processing/processed_data.py @@ -23,7 +23,7 @@ def __init__(self): """Setup the empty data container.""" self._data = {} - def add_data_point(self, xval, yval, series: str = None): + def add_data_point(self, xval: float, yval: float, series: str = None): """ Args: xval: The value of the independent variable. @@ -44,6 +44,9 @@ def series(self) -> Iterator: """ Returns: iterator: where the return values are tuples of (xvals, yvals, series) + + Yields: + The tuple (xvals, yvals, series_name) contained in self. """ - for series, data in self._data.items(): - yield np.array(data['xvals']), np.array(data['yvals']), series + for series_name, data in self._data.items(): + yield np.array(data['xvals']), np.array(data['yvals']), series_name From 1b482ace0b35d5abbac8dd617a7334fe2cee30c5 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 14:28:43 +0100 Subject: [PATCH 16/32] * Lint fixing on Calibration module wide package. --- qiskit_experiments/calibration/__init__.py | 4 +- .../analysis/calibration_analysis.py | 5 +- .../calibration/calibration_definitions.py | 98 +++++++++++-------- .../experiments/rough_amplitude.py | 31 +++--- .../calibration/parameter_value.py | 1 + test/calibration/test_experiments.py | 3 +- 6 files changed, 84 insertions(+), 58 deletions(-) diff --git a/qiskit_experiments/calibration/__init__.py b/qiskit_experiments/calibration/__init__.py index 03a4a420a3..4dff9e80bf 100644 --- a/qiskit_experiments/calibration/__init__.py +++ b/qiskit_experiments/calibration/__init__.py @@ -12,7 +12,7 @@ """Qiskit calibration root.""" -from .data_processing import DataProcessor +from .data_processing.data_processor import DataProcessor from .calibration_definitions import CalibrationsDefinition from .parameter_value import ParameterValue -from .experiments import RoughAmplitude +from .experiments.rough_amplitude import RoughAmplitude diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index ea521b87b9..36e99e5c20 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -17,12 +17,11 @@ import numpy as np from scipy import optimize -from qiskit.qobj.utils import MeasReturnType from qiskit_experiments.experiment_data import AnalysisResult +from qiskit_experiments.experiment_data import ExperimentData from qiskit_experiments.base_analysis import BaseAnalysis -from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor from qiskit_experiments.calibration.metadata import CalibrationMetadata -from qiskit_experiments import ExperimentData from qiskit_experiments.calibration.data_processing.processed_data import ProcessedData from .fit_result import FitResult diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index ef0aeaa168..fe8860cabf 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -13,18 +13,17 @@ """Class to store the results of a calibration experiments.""" import copy -import pandas as pd from datetime import datetime import dataclasses - from typing import Tuple, Union, List, Optional, Type +import pandas as pd from qiskit.circuit import Gate -from qiskit import QiskitError, QuantumCircuit +from qiskit import QuantumCircuit from qiskit.pulse import Schedule, DriveChannel, ControlChannel, MeasureChannel from qiskit.pulse.channels import PulseChannel from qiskit.circuit import Parameter - +from .exceptions import CalibrationError from .parameter_value import ParameterValue @@ -70,7 +69,7 @@ def schedules(self) -> pd.DataFrame: for name, schedule in self._schedules.items(): data.append({'name': name, 'schedule': schedule, - 'parameters': [param for param in schedule.parameters]}) + 'parameters': schedule.parameters}) return pd.DataFrame(data) @@ -117,6 +116,10 @@ def add_schedules(self, schedules: Union[Schedule, List[Schedule]]): Args: schedules: The schedule to add. + + Raises: + CalibrationError: If the parameterized channel index is not formatted + following index1.index2... """ if isinstance(schedules, Schedule): schedules = [schedules] @@ -124,13 +127,14 @@ def add_schedules(self, schedules: Union[Schedule, List[Schedule]]): for schedule in schedules: # check that channels, if parameterized, have the proper name format. + #pylint: disable = raise-missing-from for ch in schedule.channels: if isinstance(ch.index, Parameter): try: [int(_) for _ in ch.index.name.split('.')] except ValueError: - raise QiskitError('Parameterized channel must have a name ' - 'formatted following index1.index2...') + raise CalibrationError('Parameterized channel must have a name ' + 'formatted following index1.index2...') self._schedules[schedule.name] = schedule @@ -148,13 +152,18 @@ def add_parameter_value(self, param: Union[Parameter, str], standard deviation. The parameters are stored and identified by name. Args: - param: The parameter for which to add the measured value. + param: The parameter or its name for which to add the measured value. value: The value of the parameter to add. chs: The channel(s) to which the parameter applies. If None is given then the type of channels must by specified. ch_type: This parameter is only used if chs is None. In this case the value of the parameter will be set for all channels of the specified type. + + Raises: + CalibrationError: if ch_type is not given when chs are None, if the + channel type is not a ControlChannel, DriveChannel, or MeasureChannel, or + if the parameter name is not already in self. """ if isinstance(param, Parameter): name = param.name @@ -163,28 +172,28 @@ def add_parameter_value(self, param: Union[Parameter, str], if chs is None: if ch_type is None: - raise QiskitError('Channel type must be given when chs are None.') + raise CalibrationError('Channel type must be given when chs are None.') if issubclass(ch_type, ControlChannel): chs = [ch_type(_) for _ in range(self._n_uchannels)] elif issubclass(ch_type, (DriveChannel, MeasureChannel)): chs = [ch_type(_) for _ in range(self._n_qubits)] else: - raise QiskitError('Unrecognised channel type {}.'.format(ch_type)) + raise CalibrationError('Unrecognised channel type {}.'.format(ch_type)) if not isinstance(chs, list): chs = [chs] if name not in self._params: - raise QiskitError('Cannot add unknown parameter %s.' % name) - else: - for ch in chs: - if ch not in self._params[name]: - self._params[name][ch] = [value] - else: - self._params[name][ch].append(value) + raise CalibrationError('Cannot add unknown parameter %s.' % name) + + for ch in chs: + if ch not in self._params[name]: + self._params[name][ch] = [value] + else: + self._params[name][ch].append(value) - def get_channel_index(self, qubits: Tuple, ch: PulseChannel) -> int: + def get_channel_index(self, qubits: Tuple, chan: PulseChannel) -> int: """ Get the index of the parameterized channel based on the given qubits and the name of the parameter in the channel index. The name of this @@ -193,51 +202,56 @@ def get_channel_index(self, qubits: Tuple, ch: PulseChannel) -> int: Args: qubits: The qubits for which we want to obtain the channel index. - ch: The channel with a parameterized name. + chan: The channel with a parameterized name. Returns: index: The index of the channel. For example, if qubits=(int, int) and the channel is a u channel with parameterized index name 'x.y' where x and y the method returns the u_channel corresponding to qubits (qubits[1], qubits[0]). + + Raises: + CalibrationError: if the number of qubits is incorrect, if the + number of inferred ControlChannels is not correct, or if ch is not + a DriveChannel, MeasureChannel, or ControlChannel. """ - if isinstance(ch.index, Parameter): - indices = [int(_) for _ in ch.index.name.split('.')] - ch_qubits = tuple([qubits[_] for _ in indices]) + if isinstance(chan.index, Parameter): + indices = [int(_) for _ in chan.index.name.split('.')] + ch_qubits = tuple(qubits[_] for _ in indices) - if isinstance(ch, DriveChannel): + if isinstance(chan, DriveChannel): if len(ch_qubits) != 1: - raise QiskitError('Too many qubits for drive channel: ' + raise CalibrationError('Too many qubits for drive channel: ' 'got %i expecting 1.' % len(ch_qubits)) ch_ = self._config.drive(ch_qubits[0]) - elif isinstance(ch, MeasureChannel): + elif isinstance(chan, MeasureChannel): if len(ch_qubits) != 1: - raise QiskitError('Too many qubits for drive channel: ' + raise CalibrationError('Too many qubits for drive channel: ' 'got %i expecting 1.' % len(ch_qubits)) ch_ = self._config.measure(ch_qubits[0]) - elif isinstance(ch, ControlChannel): + elif isinstance(chan, ControlChannel): chs_ = self._config.control(ch_qubits) if len(chs_) != 1: - raise QiskitError('Ambiguous number of control channels for ' - 'qubits {} and {}.'.format(qubits, ch.name)) + raise CalibrationError('Ambiguous number of control channels for ' + 'qubits {} and {}.'.format(qubits, chan.name)) ch_ = chs_[0] else: chs = tuple(_.__name__ for _ in [DriveChannel, ControlChannel, MeasureChannel]) - raise QiskitError('Channel must be of type {}.'.format(chs)) + raise CalibrationError('Channel must be of type {}.'.format(chs)) return ch_.index else: - return ch.index + return chan.index - def parameter_value(self, name: str, ch: PulseChannel, valid_only: bool = True, + def parameter_value(self, name: str, chan: PulseChannel, valid_only: bool = True, group: str = 'default', cutoff_date: datetime = None) -> Union[int, float, complex]: """ @@ -245,7 +259,7 @@ def parameter_value(self, name: str, ch: PulseChannel, valid_only: bool = True, Args: name: The name of the parameter to get. - ch: The channel for which we want the value of the parameter. + chan: The channel for which we want the value of the parameter. valid_only: Use only parameters marked as valid. group: The calibration group from which to draw the parameters. If not specifies this defaults to the 'default' group. @@ -255,13 +269,17 @@ def parameter_value(self, name: str, ch: PulseChannel, valid_only: bool = True, Returns: value: The value of the parameter. - """ + Raises: + CalibrationError: if there is no parameter value for the given parameter name + and pulse channel. + """ + #pylint: disable = raise-missing-from try: if valid_only: - candidates = [p for p in self._params[name][ch] if p.valid] + candidates = [p for p in self._params[name][chan] if p.valid] else: - candidates = self._params[name][ch] + candidates = self._params[name][chan] candidates = [_ for _ in candidates if _.group == group] @@ -272,7 +290,7 @@ def parameter_value(self, name: str, ch: PulseChannel, valid_only: bool = True, return candidates[-1].value except KeyError: - raise QiskitError('No parameter value for %s and channel %s' % (name, ch.name)) + raise CalibrationError('No parameter value for %s and channel %s' % (name, chan.name)) def get_schedule(self, name: str, qubits: Tuple[int, ...], free_params: List[str] = None, group: Optional[str] = 'default') -> Schedule: @@ -289,13 +307,13 @@ def get_schedule(self, name: str, qubits: Tuple[int, ...], except for those specified by free_params. Raises: - QiskitError: if the name of the schedule is not known. + CalibrationError: if the name of the schedule is not known. """ # Get the schedule and deepcopy it to prevent binding from removing # the parametric nature of the schedule. if name not in self._schedules: - raise QiskitError('Schedule %s is not defined.' % name) + raise CalibrationError('Schedule %s is not defined.' % name) sched = copy.deepcopy(self._schedules[name]) @@ -336,6 +354,8 @@ def get_circuit(self, name: str, qubits: Tuple, free_params: List[str] = None, not going to be used. Returns: + A quantum circuit in which the parameter values have been assigned aside from + those explicitly specified in free_params. """ if schedule is None: schedule = self.get_schedule(name, qubits, free_params, group) diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 1408f6a414..daf18cc7dc 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -12,20 +12,21 @@ """Rough amplitude calibration.""" -import numpy as np +from dataclasses import asdict from datetime import datetime - from typing import Dict, List, Optional -from dataclasses import asdict +import numpy as np +from qiskit.pulse import DriveChannel +from qiskit import QuantumCircuit from qiskit_experiments.base_experiment import BaseExperiment -from qiskit_experiments.calibration.analysis import CosineFit, utils +from qiskit_experiments.calibration.analysis import CosineFit +from qiskit_experiments.calibration.analysis.utils import get_period_fraction from qiskit_experiments.calibration.metadata import CalibrationMetadata -from qiskit_experiments.calibration import CalibrationsDefinition -from qiskit_experiments.calibration import DataProcessor -from qiskit_experiments.calibration import ParameterValue +from qiskit_experiments.calibration.calibration_definitions import CalibrationsDefinition +from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor +from qiskit_experiments.calibration.parameter_value import ParameterValue from qiskit_experiments import ExperimentData -from qiskit.pulse import DriveChannel class RoughAmplitude(BaseExperiment): @@ -66,13 +67,17 @@ def __init__(self, self._calibration_group = group self._gate_name = gate_name - def circuits(self, backend=None, **circuit_options): + def circuits(self, backend=None, **circuit_options) -> List[QuantumCircuit]: """ - Create the circuits for a rough rabi amplitude calibration. + Create the circuits for a rough rabi amplitude calibration and add the + required metadata. + Args: - backend: Not used. - circuit_options: + backend (Backend): Not used. + circuit_options: Not used. + Returns: + A list of quantum circuits where a parameter is scanned. """ circuits = [] for amplitude in self.amplitudes: @@ -117,7 +122,7 @@ def update_calibrations(self, experiment_data: ExperimentData, group=self._calibration_group) phase = np.exp(1.0j*np.angle(amp)) - value = phase*utils.get_period_fraction(self.__analysis_class__, angle, fit_result) + value = phase*get_period_fraction(self.__analysis_class__, angle, fit_result) param_val = ParameterValue(value, datetime.now(), exp_id=experiment_data.experiment_id, group=self._calibration_group) diff --git a/qiskit_experiments/calibration/parameter_value.py b/qiskit_experiments/calibration/parameter_value.py index 28a86f62a0..4c68d9eb6b 100644 --- a/qiskit_experiments/calibration/parameter_value.py +++ b/qiskit_experiments/calibration/parameter_value.py @@ -19,6 +19,7 @@ @dataclass class ParameterValue: + """A data class to store parameter values.""" # Value assumed by the parameter value: Union[int, float] = None diff --git a/test/calibration/test_experiments.py b/test/calibration/test_experiments.py index f0ad39b284..cf28eddeb1 100644 --- a/test/calibration/test_experiments.py +++ b/test/calibration/test_experiments.py @@ -15,6 +15,7 @@ from qiskit_experiments.calibration import CalibrationsDefinition from qiskit_experiments.calibration import ParameterValue from qiskit_experiments.calibration import RoughAmplitude +from qiskit_experiments.calibration import DataProcessor import qiskit.pulse as pulse from qiskit.test import QiskitTestCase from qiskit.pulse import Drag, DriveChannel @@ -63,7 +64,7 @@ def test_rough_amplitude(self): qubit = 3 amps = [-0.5, 0.5] - amp = RoughAmplitude(qubit, self.cals, 'xp', 'amp_xp', amps) + amp = RoughAmplitude(qubit, self.cals, 'xp', 'amp_xp', DataProcessor(), amps) circs = amp.transpiled_circuits(self.backend) # Check that there is a gate on qubit 3. From 4cea830243764059315818fb4593cc45dd1eae2c Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Fri, 19 Mar 2021 14:52:05 +0100 Subject: [PATCH 17/32] * Lint on tests. * Added test for the CalibrationsDefinition. --- test/calibration/test_analysis.py | 1 - .../test_calibrations_definition.py | 48 +++++++++++++++++++ test/calibration/test_data_processing.py | 10 ++-- test/calibration/test_experiments.py | 9 ++-- test/calibration/test_fitting.py | 2 +- 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/test/calibration/test_analysis.py b/test/calibration/test_analysis.py index b3d95fa32a..137e993968 100644 --- a/test/calibration/test_analysis.py +++ b/test/calibration/test_analysis.py @@ -11,4 +11,3 @@ # that they have been altered from the originals. """Test calibration analysis routines.""" - diff --git a/test/calibration/test_calibrations_definition.py b/test/calibration/test_calibrations_definition.py index fce351056e..0dd5edff89 100644 --- a/test/calibration/test_calibrations_definition.py +++ b/test/calibration/test_calibrations_definition.py @@ -11,3 +11,51 @@ # that they have been altered from the originals. """Test the class that holds the calibrations.""" + +from datetime import datetime + +from qiskit.test import QiskitTestCase +from qiskit.test.mock import FakeAlmaden +from qiskit.circuit import Parameter +import qiskit.pulse as pulse +from qiskit.pulse import Drag, DriveChannel +from qiskit_experiments.calibration import CalibrationsDefinition +from qiskit_experiments.calibration import ParameterValue + + +class TestCalibrationsDefinition(QiskitTestCase): + """Class to test the calibration definitions.""" + + def test_simple_schedule(self): + """Test that we can add a schedule.""" + + sigma = Parameter('σ') + d0 = Parameter('0') + amp = Parameter('A') + beta = Parameter('β') + + with pulse.build(name='xp') as xp: + pulse.play(Drag(160, amp, sigma, beta), DriveChannel(d0)) + + cals = CalibrationsDefinition(FakeAlmaden()) + cals.add_schedules([xp]) + + # Check that there is a schedule in cals + self.assertEqual(len(cals.schedules()), 1) + + # Check that we can get the schedule for different drive parameters. + sched = cals.get_schedule('xp', (0, ), ['σ', 'A', 'β']) + self.assertEqual(sched.instructions[0][1].channel, DriveChannel(0)) + + sched = cals.get_schedule('xp', (3, ), ['σ', 'A', 'β']) + self.assertEqual(sched.instructions[0][1].channel, DriveChannel(3)) + + for param in [sigma, amp, beta]: + self.assertTrue(param in sched.parameters) + + # Add a parameter + cals.add_parameter_value('A', ParameterValue(0.123, datetime.now()), chs=[DriveChannel(0)]) + + sched = cals.get_schedule('xp', (0, ), ['σ', 'β']) + self.assertEqual(sched.instructions[0][1].pulse.amp, 0.123) + self.assertTrue(amp not in sched.parameters) diff --git a/test/calibration/test_data_processing.py b/test/calibration/test_data_processing.py index 32ca8f7b97..7a8ae066ef 100644 --- a/test/calibration/test_data_processing.py +++ b/test/calibration/test_data_processing.py @@ -12,16 +12,16 @@ """Data processor tests.""" -from qiskit_experiments import ExperimentData -from qiskit_experiments.base_experiment import BaseExperiment -from qiskit_experiments.calibration import DataProcessor -from qiskit_experiments.calibration.data_processing import (SystemKernel, SystemDiscriminator, - ToReal, ToImag, Population) from qiskit.result.models import ExperimentResultData, ExperimentResult from qiskit.result import Result from qiskit.test import QiskitTestCase from qiskit.qobj.utils import MeasLevel from qiskit.qobj.common import QobjExperimentHeader +from qiskit_experiments import ExperimentData +from qiskit_experiments.base_experiment import BaseExperiment +from qiskit_experiments.calibration import DataProcessor +from qiskit_experiments.calibration.data_processing import (SystemKernel, SystemDiscriminator, + ToReal, ToImag, Population) class FakeExperiment(BaseExperiment): diff --git a/test/calibration/test_experiments.py b/test/calibration/test_experiments.py index cf28eddeb1..eef5b7e9d0 100644 --- a/test/calibration/test_experiments.py +++ b/test/calibration/test_experiments.py @@ -12,15 +12,15 @@ """Test the rough amplitude experiment.""" -from qiskit_experiments.calibration import CalibrationsDefinition -from qiskit_experiments.calibration import ParameterValue -from qiskit_experiments.calibration import RoughAmplitude -from qiskit_experiments.calibration import DataProcessor import qiskit.pulse as pulse from qiskit.test import QiskitTestCase from qiskit.pulse import Drag, DriveChannel from qiskit.test.mock import FakeAlmaden from qiskit.circuit import Parameter, Gate +from qiskit_experiments.calibration import CalibrationsDefinition +from qiskit_experiments.calibration import ParameterValue +from qiskit_experiments.calibration import RoughAmplitude +from qiskit_experiments.calibration import DataProcessor class TestCalibrationExperiments(QiskitTestCase): @@ -31,7 +31,6 @@ def setUp(self): self.backend = FakeAlmaden() self.cals = CalibrationsDefinition(self.backend) - sq_freq = Parameter('freq') sigma_xp = Parameter('σ_xp') d0 = Parameter('0') amp_xp = Parameter('amp_xp') diff --git a/test/calibration/test_fitting.py b/test/calibration/test_fitting.py index de7a36cb9e..3e9d5dd7a3 100644 --- a/test/calibration/test_fitting.py +++ b/test/calibration/test_fitting.py @@ -14,8 +14,8 @@ import numpy as np -from qiskit_experiments.calibration.analysis.trigonometric import freq_guess from qiskit.test import QiskitTestCase +from qiskit_experiments.calibration.analysis.trigonometric import freq_guess class TestCalibrationFitting(QiskitTestCase): From 0de5785664eb3cdc77759aa6fbb6706755429be8 Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 24 Mar 2021 10:58:27 +0100 Subject: [PATCH 18/32] Update qiskit_experiments/calibration/data_processing/__init__.py Co-authored-by: Naoki Kanazawa --- .../calibration/data_processing/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/calibration/data_processing/__init__.py index 800bde672c..86d61f9838 100644 --- a/qiskit_experiments/calibration/data_processing/__init__.py +++ b/qiskit_experiments/calibration/data_processing/__init__.py @@ -13,7 +13,15 @@ """Qiskit experiments calibration data processing roots.""" from .base import DataAction -from .nodes import SystemKernel +from .nodes import ( + # data acquisition node + SystemDiscriminator, + SystemKernel, + # value formatter node + Population, + ToImag, + ToReal +) from .nodes import SystemDiscriminator from .nodes import ToReal from .nodes import ToImag From ed639285721c0407f34753f3d10933d990bd188a Mon Sep 17 00:00:00 2001 From: Daniel Egger <38065505+eggerdj@users.noreply.github.com> Date: Wed, 24 Mar 2021 11:02:02 +0100 Subject: [PATCH 19/32] Update qiskit_experiments/calibration/calibration_definitions.py Co-authored-by: Naoki Kanazawa --- qiskit_experiments/calibration/calibration_definitions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index fe8860cabf..7f6702ece2 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -181,7 +181,10 @@ def add_parameter_value(self, param: Union[Parameter, str], else: raise CalibrationError('Unrecognised channel type {}.'.format(ch_type)) - if not isinstance(chs, list): + try: + chs = list(chs) + except TypeError: + chs = [chs] chs = [chs] if name not in self._params: From c67acbd84d9a1092951bcfc843b5453ff3e6c68a Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 11:23:04 +0100 Subject: [PATCH 20/32] * Introduced BaseCalibrationExperiment class * Add class variable __calibration_objective__ * Refactored package __init__ --- .../calibration/data_processing/__init__.py | 6 +-- .../experiments/BaseCalibrationExperiment.py | 39 +++++++++++++++++++ .../experiments/rough_amplitude.py | 26 +++++++------ 3 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/calibration/data_processing/__init__.py index 86d61f9838..db927e91ac 100644 --- a/qiskit_experiments/calibration/data_processing/__init__.py +++ b/qiskit_experiments/calibration/data_processing/__init__.py @@ -17,14 +17,12 @@ # data acquisition node SystemDiscriminator, SystemKernel, + # value formatter node Population, ToImag, ToReal ) -from .nodes import SystemDiscriminator -from .nodes import ToReal -from .nodes import ToImag -from .nodes import Population + from .data_processor import DataProcessor from .processed_data import ProcessedData diff --git a/qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py b/qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py new file mode 100644 index 0000000000..f0fec1ca48 --- /dev/null +++ b/qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py @@ -0,0 +1,39 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Base experiment class for calibration.""" + +from abc import abstractmethod + +from qiskit_experiments.base_experiment import BaseExperiment +from qiskit_experiments import ExperimentData + + +class BaseCalibrationExperiment(BaseExperiment): + + # Gates and pulse parameters to update + __calibration_objective__ = { + 'gates': [], + 'options': [], + 'parameter_name': None + } + + @abstractmethod + def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): + """ + Update the parameters in the calibration table. + + Args: + experiment_data: The experiment data to use to update the pulse amplitudes. + index: The index of analysis result to use in experiment_data. If this is not + specified then the latest added analysis result is used. + """ diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index daf18cc7dc..94be3a9b71 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -14,12 +14,11 @@ from dataclasses import asdict from datetime import datetime -from typing import Dict, List, Optional +from typing import List, Optional import numpy as np from qiskit.pulse import DriveChannel from qiskit import QuantumCircuit -from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.calibration.analysis import CosineFit from qiskit_experiments.calibration.analysis.utils import get_period_fraction from qiskit_experiments.calibration.metadata import CalibrationMetadata @@ -27,14 +26,22 @@ from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor from qiskit_experiments.calibration.parameter_value import ParameterValue from qiskit_experiments import ExperimentData +from . import BaseCalibrationExperiment -class RoughAmplitude(BaseExperiment): +class RoughAmplitude(BaseCalibrationExperiment): """Rough amplitude calibration that scans the amplitude.""" # Analysis class for experiment __analysis_class__ = CosineFit + # Gates and pulse parameters to update + __calibration_objective__ = { + 'gates': ['x90p', 'xp'], + 'options': [np.pi/2, np.pi], + 'parameter_name': 'amp' + } + def __init__(self, qubit: int, cals: CalibrationsDefinition, @@ -98,25 +105,20 @@ def circuits(self, backend=None, **circuit_options) -> List[QuantumCircuit]: return circuits - def update_calibrations(self, experiment_data: ExperimentData, - update_pulses: Optional[Dict[str, float]] = None, index: int = -1): + def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): """ Updates the amplitude of the pulses. This will preserve the existing phase. Args: experiment_data: The experiment data to use to update the pulse amplitudes. - update_pulses: The pulse to update. The key is the name of the pulse parameter and - the value is the angle to extract from the cosine fit. For example, {'amp_xp': - np.pi, 'amp_x90p': np.pi/2} will update the amplitude ot the xp and x90p pulses. index: The index of analysis result to use in experiment_data. If this is not specified then the latest added analysis result is used. """ - if update_pulses is None: - update_pulses = {self._gate_name: np.pi/2} - fit_result = experiment_data.analysis_result(index)['default'] - for param_name, angle in update_pulses.items(): + for idx, gate in enumerate(self.__calibration_objective__['gates']): + angle = self.__calibration_objective__['options'][idx] + param_name = self.__calibration_objective__['parameter_name'] amp = self._cal_def.parameter_value(param_name, DriveChannel(self._qubit), group=self._calibration_group) From cd4fde182e3e5019fdfc9155c08fd5ae3cba958b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 11:54:25 +0100 Subject: [PATCH 21/32] * Renamed a file. * Moved amplitudes in RoughAmplitude to a run-time variable. --- .../calibration/calibration_definitions.py | 7 +++--- ...ment.py => base_calibration_experiment.py} | 0 .../experiments/rough_amplitude.py | 24 +++++++++---------- 3 files changed, 14 insertions(+), 17 deletions(-) rename qiskit_experiments/calibration/experiments/{BaseCalibrationExperiment.py => base_calibration_experiment.py} (100%) diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index 7f6702ece2..db829e4f82 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -181,10 +181,9 @@ def add_parameter_value(self, param: Union[Parameter, str], else: raise CalibrationError('Unrecognised channel type {}.'.format(ch_type)) - try: - chs = list(chs) - except TypeError: - chs = [chs] + try: + chs = list(chs) + except TypeError: chs = [chs] if name not in self._params: diff --git a/qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py b/qiskit_experiments/calibration/experiments/base_calibration_experiment.py similarity index 100% rename from qiskit_experiments/calibration/experiments/BaseCalibrationExperiment.py rename to qiskit_experiments/calibration/experiments/base_calibration_experiment.py diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 94be3a9b71..4165bfff0c 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -26,7 +26,7 @@ from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor from qiskit_experiments.calibration.parameter_value import ParameterValue from qiskit_experiments import ExperimentData -from . import BaseCalibrationExperiment +from .base_calibration_experiment import BaseCalibrationExperiment class RoughAmplitude(BaseCalibrationExperiment): @@ -48,7 +48,6 @@ def __init__(self, gate_name: str, parameter_name: str, data_processor: DataProcessor, - amplitudes = List[float], group: Optional[str] = 'default'): """ Args: @@ -60,16 +59,13 @@ def __init__(self, parameter_name: The name of the parameter to sweep, typically this will be the amplitude of the pulse. data_processor: The data processor used to process the measured data. - amplitudes: The amplitudes over which to sweep. group: The calibration group that will be updated which defaults to 'default'. """ circuit_options = ['initial_layout'] super().__init__([qubit], self.__class__.__name__, circuit_options) self._cal_def = cals - self.amplitudes = amplitudes self.parameter = parameter_name - self._qubit = qubit self._data_processor = data_processor self._calibration_group = group self._gate_name = gate_name @@ -81,25 +77,27 @@ def circuits(self, backend=None, **circuit_options) -> List[QuantumCircuit]: Args: backend (Backend): Not used. - circuit_options: Not used. + circuit_options: Can contain 'amplitudes' a list of floats specifying + the amplitude values to scan. If amplitudes is not given the free + parameter will be scanned from -0.9 to 0.9 in 51 steps. Returns: A list of quantum circuits where a parameter is scanned. """ + template = self._cal_def.get_circuit(self._gate_name, self._physical_qubits, [self.parameter]) + template.measure(0, 0) + template.name = 'circuit' + circuits = [] - for amplitude in self.amplitudes: + for amplitude in circuit_options.get('amplitudes', np.linspace(-0.9, 0.9, 51)): meta = CalibrationMetadata( experiment_type=self._type, pulse_schedule_name=self.__class__.__name__, x_values=amplitude, - qubits=self._qubit + qubits=self._physical_qubits ) - template_qc = self._cal_def.get_circuit(self._gate_name, (self._qubit,), [self.parameter]) - template_qc.measure(0, 0) - template_qc.name = 'circuit' - - qc = template_qc.assign_parameters({template_qc.parameters[0]: amplitude}) + qc = template.assign_parameters({template.parameters[0]: amplitude}) qc.metadata = asdict(meta) circuits.append(qc) From 828788ba46751d2565cc670e6126be420c1aeae7 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 12:09:43 +0100 Subject: [PATCH 22/32] * Fixed docstring. --- qiskit_experiments/calibration/parameter_value.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/calibration/parameter_value.py b/qiskit_experiments/calibration/parameter_value.py index 4c68d9eb6b..0f257f0325 100644 --- a/qiskit_experiments/calibration/parameter_value.py +++ b/qiskit_experiments/calibration/parameter_value.py @@ -27,7 +27,7 @@ class ParameterValue: # Data time when the value of the parameter was generated date_time: datetime = datetime.fromtimestamp(0) - # An enum indicating if the parameter is valid + # A bool indicating if the parameter is valid valid: bool = True # The experiment from which the value of this parameter was generated. From 98afb2e3e6abb8a082a4cc8fdb66a9fa38d2e547 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 13:28:42 +0100 Subject: [PATCH 23/32] * Fixed test by adding amplitudes to the circuit options for RoughAmplitude. --- qiskit_experiments/calibration/experiments/rough_amplitude.py | 2 +- test/calibration/test_experiments.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 4165bfff0c..041fce0df8 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -61,7 +61,7 @@ def __init__(self, data_processor: The data processor used to process the measured data. group: The calibration group that will be updated which defaults to 'default'. """ - circuit_options = ['initial_layout'] + circuit_options = ['initial_layout', 'amplitudes'] super().__init__([qubit], self.__class__.__name__, circuit_options) self._cal_def = cals diff --git a/test/calibration/test_experiments.py b/test/calibration/test_experiments.py index eef5b7e9d0..839e692a0f 100644 --- a/test/calibration/test_experiments.py +++ b/test/calibration/test_experiments.py @@ -63,8 +63,8 @@ def test_rough_amplitude(self): qubit = 3 amps = [-0.5, 0.5] - amp = RoughAmplitude(qubit, self.cals, 'xp', 'amp_xp', DataProcessor(), amps) - circs = amp.transpiled_circuits(self.backend) + amp = RoughAmplitude(qubit, self.cals, 'xp', 'amp_xp', DataProcessor()) + circs = amp.transpiled_circuits(self.backend, amplitudes=amps) # Check that there is a gate on qubit 3. self.assertEqual(circs[1].data[0][1][0].index, qubit) From c7a388846482a737764525abe019196941b3a01e Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 14:12:52 +0100 Subject: [PATCH 24/32] * Changed _plot to plot in calibration_analysis. --- .../calibration/analysis/calibration_analysis.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 36e99e5c20..81180e68ac 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -164,11 +164,11 @@ def _run_analysis(self, experiment_data: ExperimentData, qubit=0, figures = None if plot and HAS_MATPLOTLIB: - figures = self._plot(qubit, results, **kwargs) + figures = self.plot(qubit, results, **kwargs) return results, figures - def _plot(self, qubit, results, **kwargs) -> List: + def plot(self, qubit, results, **kwargs) -> List: """ Plot the result. From 157fcf5c6c11a59f031b0612b677a5fb4c2c713b Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 14:20:34 +0100 Subject: [PATCH 25/32] * Removed self.backend from CalibrationsDefinition. --- qiskit_experiments/calibration/calibration_definitions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index db829e4f82..3b31d1d927 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -49,7 +49,6 @@ def __init__(self, backend): self._config = backend.configuration() self._params = {'qubit_freq': {}} self._schedules = {} - self.backend = backend # Populate the qubit frequency estimates now = datetime.now() From 724537464213cced40164745df3dd4fbc87482b8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 14:29:08 +0100 Subject: [PATCH 26/32] * Added timestamp from the backend properties. --- qiskit_experiments/calibration/calibration_definitions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index 3b31d1d927..ac70731a1c 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -46,14 +46,15 @@ def __init__(self, backend): self._n_qubits = backend.configuration().num_qubits self._n_uchannels = backend.configuration().n_uchannels + self._properties = backend.properties() self._config = backend.configuration() self._params = {'qubit_freq': {}} self._schedules = {} # Populate the qubit frequency estimates - now = datetime.now() for qubit, freq in enumerate(backend.defaults().qubit_freq_est): - val = ParameterValue(freq, now) + timestamp = backend.properties().qubit_property(qubit)['frequency'][1] + val = ParameterValue(freq, timestamp) self.add_parameter_value('qubit_freq', val, DriveChannel(qubit)) def schedules(self) -> pd.DataFrame: From 09420c9c57152786fa534ff0ed89e10c78de7898 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 16:40:44 +0100 Subject: [PATCH 27/32] * Renamed _ to index. --- qiskit_experiments/calibration/calibration_definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/calibration/calibration_definitions.py b/qiskit_experiments/calibration/calibration_definitions.py index ac70731a1c..c8e883cd22 100644 --- a/qiskit_experiments/calibration/calibration_definitions.py +++ b/qiskit_experiments/calibration/calibration_definitions.py @@ -131,7 +131,7 @@ def add_schedules(self, schedules: Union[Schedule, List[Schedule]]): for ch in schedule.channels: if isinstance(ch.index, Parameter): try: - [int(_) for _ in ch.index.name.split('.')] + [int(index) for index in ch.index.name.split('.')] except ValueError: raise CalibrationError('Parameterized channel must have a name ' 'formatted following index1.index2...') From eb26625be5529101fc1d26c324bc5748b23fb20f Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 17:08:22 +0100 Subject: [PATCH 28/32] * Removed test_analysis. * Fixed issue with the qubits in RoughAmplitude. --- .../calibration/experiments/rough_amplitude.py | 5 +++-- test/calibration/test_analysis.py | 13 ------------- 2 files changed, 3 insertions(+), 15 deletions(-) delete mode 100644 test/calibration/test_analysis.py diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 041fce0df8..e5071c18b3 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -118,7 +118,7 @@ def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): angle = self.__calibration_objective__['options'][idx] param_name = self.__calibration_objective__['parameter_name'] amp = self._cal_def.parameter_value(param_name, - DriveChannel(self._qubit), + DriveChannel(self._physical_qubits[0]), group=self._calibration_group) phase = np.exp(1.0j*np.angle(amp)) @@ -127,4 +127,5 @@ def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): param_val = ParameterValue(value, datetime.now(), exp_id=experiment_data.experiment_id, group=self._calibration_group) - self._cal_def.add_parameter_value(param_name, param_val, DriveChannel(self._qubit)) + self._cal_def.add_parameter_value(param_name, param_val, + DriveChannel(self._physical_qubits[0])) diff --git a/test/calibration/test_analysis.py b/test/calibration/test_analysis.py deleted file mode 100644 index 137e993968..0000000000 --- a/test/calibration/test_analysis.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test calibration analysis routines.""" From a5e5c7686ef8e52efdc525334ee1ad334bb3f587 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 17:30:31 +0100 Subject: [PATCH 29/32] * Moved the data processing up one level. * Add fixes in RoughAmplitude parameter updating. --- qiskit_experiments/calibration/__init__.py | 1 - .../calibration/analysis/calibration_analysis.py | 4 ++-- .../calibration/experiments/rough_amplitude.py | 6 +++--- .../{calibration => }/data_processing/__init__.py | 0 .../{calibration => }/data_processing/base.py | 0 .../{calibration => }/data_processing/data_processor.py | 0 .../{calibration => }/data_processing/nodes.py | 0 .../{calibration => }/data_processing/processed_data.py | 0 test/calibration/test_data_processing.py | 4 ++-- 9 files changed, 7 insertions(+), 8 deletions(-) rename qiskit_experiments/{calibration => }/data_processing/__init__.py (100%) rename qiskit_experiments/{calibration => }/data_processing/base.py (100%) rename qiskit_experiments/{calibration => }/data_processing/data_processor.py (100%) rename qiskit_experiments/{calibration => }/data_processing/nodes.py (100%) rename qiskit_experiments/{calibration => }/data_processing/processed_data.py (100%) diff --git a/qiskit_experiments/calibration/__init__.py b/qiskit_experiments/calibration/__init__.py index 4dff9e80bf..2836e63390 100644 --- a/qiskit_experiments/calibration/__init__.py +++ b/qiskit_experiments/calibration/__init__.py @@ -12,7 +12,6 @@ """Qiskit calibration root.""" -from .data_processing.data_processor import DataProcessor from .calibration_definitions import CalibrationsDefinition from .parameter_value import ParameterValue from .experiments.rough_amplitude import RoughAmplitude diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 81180e68ac..2bda47052a 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -20,9 +20,9 @@ from qiskit_experiments.experiment_data import AnalysisResult from qiskit_experiments.experiment_data import ExperimentData from qiskit_experiments.base_analysis import BaseAnalysis -from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor +from qiskit_experiments.data_processing import DataProcessor from qiskit_experiments.calibration.metadata import CalibrationMetadata -from qiskit_experiments.calibration.data_processing.processed_data import ProcessedData +from qiskit_experiments.data_processing.processed_data import ProcessedData from .fit_result import FitResult diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index e5071c18b3..909d10569d 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -23,7 +23,7 @@ from qiskit_experiments.calibration.analysis.utils import get_period_fraction from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments.calibration.calibration_definitions import CalibrationsDefinition -from qiskit_experiments.calibration.data_processing.data_processor import DataProcessor +from qiskit_experiments.data_processing import DataProcessor from qiskit_experiments.calibration.parameter_value import ParameterValue from qiskit_experiments import ExperimentData from .base_calibration_experiment import BaseCalibrationExperiment @@ -39,7 +39,7 @@ class RoughAmplitude(BaseCalibrationExperiment): __calibration_objective__ = { 'gates': ['x90p', 'xp'], 'options': [np.pi/2, np.pi], - 'parameter_name': 'amp' + 'parameter_name': ['amp_x90p', 'amp_xp'] } def __init__(self, @@ -116,7 +116,7 @@ def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): for idx, gate in enumerate(self.__calibration_objective__['gates']): angle = self.__calibration_objective__['options'][idx] - param_name = self.__calibration_objective__['parameter_name'] + param_name = self.__calibration_objective__['parameter_name'][idx] amp = self._cal_def.parameter_value(param_name, DriveChannel(self._physical_qubits[0]), group=self._calibration_group) diff --git a/qiskit_experiments/calibration/data_processing/__init__.py b/qiskit_experiments/data_processing/__init__.py similarity index 100% rename from qiskit_experiments/calibration/data_processing/__init__.py rename to qiskit_experiments/data_processing/__init__.py diff --git a/qiskit_experiments/calibration/data_processing/base.py b/qiskit_experiments/data_processing/base.py similarity index 100% rename from qiskit_experiments/calibration/data_processing/base.py rename to qiskit_experiments/data_processing/base.py diff --git a/qiskit_experiments/calibration/data_processing/data_processor.py b/qiskit_experiments/data_processing/data_processor.py similarity index 100% rename from qiskit_experiments/calibration/data_processing/data_processor.py rename to qiskit_experiments/data_processing/data_processor.py diff --git a/qiskit_experiments/calibration/data_processing/nodes.py b/qiskit_experiments/data_processing/nodes.py similarity index 100% rename from qiskit_experiments/calibration/data_processing/nodes.py rename to qiskit_experiments/data_processing/nodes.py diff --git a/qiskit_experiments/calibration/data_processing/processed_data.py b/qiskit_experiments/data_processing/processed_data.py similarity index 100% rename from qiskit_experiments/calibration/data_processing/processed_data.py rename to qiskit_experiments/data_processing/processed_data.py diff --git a/test/calibration/test_data_processing.py b/test/calibration/test_data_processing.py index 7a8ae066ef..d8a395c416 100644 --- a/test/calibration/test_data_processing.py +++ b/test/calibration/test_data_processing.py @@ -20,8 +20,8 @@ from qiskit_experiments import ExperimentData from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments.calibration import DataProcessor -from qiskit_experiments.calibration.data_processing import (SystemKernel, SystemDiscriminator, - ToReal, ToImag, Population) +from qiskit_experiments.data_processing import (SystemKernel, SystemDiscriminator, + ToReal, ToImag, Population) class FakeExperiment(BaseExperiment): From b75f2ddead069183b50f407bc2a232935662decb Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 18:04:28 +0100 Subject: [PATCH 30/32] * Renamed SystemKernel to Kernel and SystemDiscriminator to Discriminator. --- qiskit_experiments/data_processing/nodes.py | 28 +++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/qiskit_experiments/data_processing/nodes.py b/qiskit_experiments/data_processing/nodes.py index 22e9c7beec..276b295407 100644 --- a/qiskit_experiments/data_processing/nodes.py +++ b/qiskit_experiments/data_processing/nodes.py @@ -21,10 +21,11 @@ @kernel @prev_node() -class SystemKernel(DataAction): - """Backend system kernel.""" +class Kernel(DataAction): + """User provided kernel.""" - def __init__(self, name: Optional[str] = None): + def __init__(self, kernel, name: Optional[str] = None): + self.kernel = kernel self.name = name super().__init__() @@ -33,20 +34,25 @@ def process(self, data: Dict[str, Any]): Args: data: The data dictionary to process. """ + if 'memory' not in data: + raise CalibrationError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') + + data['memory'] = self.kernel.kernel(np.array(data['memory'])) @discriminator -@prev_node(SystemKernel) -class SystemDiscriminator(DataAction): +@prev_node(Kernel) +class Discriminator(DataAction): """Backend system discriminator.""" - def __init__(self, discriminator_, name: Optional[str] = None): + def __init__(self, discriminator, name: Optional[str] = None): """ Args: - discriminator_: The discriminator used to transform the data to counts. + discriminator: The discriminator used to transform the data to counts. For example, transform IQ data to counts. """ - self.discriminator = discriminator_ + self.discriminator = discriminator self.name = name super().__init__() @@ -68,7 +74,7 @@ def process(self, data: Dict[str, Any]): @iq_data -@prev_node(SystemKernel) +@prev_node(Kernel) class ToReal(DataAction): """IQ data post-processing. This returns real part of IQ data.""" @@ -113,7 +119,7 @@ def process(self, data: Dict[str, Any]): data['memory'] = new_mem @iq_data -@prev_node(SystemKernel) +@prev_node(Kernel) class ToImag(DataAction): """IQ data post-processing. This returns imaginary part of IQ data.""" @@ -157,7 +163,7 @@ def process(self, data: Dict[str, Any]): @population -@prev_node(SystemDiscriminator) +@prev_node(Discriminator) class Population(DataAction): """Count data post processing. This returns population.""" From 38d51cd95ab1bf70608a09a501b82cdb91ef3b12 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Wed, 24 Mar 2021 18:50:13 +0100 Subject: [PATCH 31/32] * Added DataProcessingException. * Fixed lint. --- .../analysis/calibration_analysis.py | 2 +- .../base_calibration_experiment.py | 1 + .../experiments/rough_amplitude.py | 2 +- .../data_processing/__init__.py | 4 +- qiskit_experiments/data_processing/base.py | 12 ++--- .../data_processing/data_processor.py | 14 ++--- .../data_processing/exceptions.py | 28 ++++++++++ qiskit_experiments/data_processing/nodes.py | 52 +++++++++++-------- test/calibration/test_data_processing.py | 23 +++++--- 9 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 qiskit_experiments/data_processing/exceptions.py diff --git a/qiskit_experiments/calibration/analysis/calibration_analysis.py b/qiskit_experiments/calibration/analysis/calibration_analysis.py index 2bda47052a..63622e4017 100644 --- a/qiskit_experiments/calibration/analysis/calibration_analysis.py +++ b/qiskit_experiments/calibration/analysis/calibration_analysis.py @@ -20,7 +20,7 @@ from qiskit_experiments.experiment_data import AnalysisResult from qiskit_experiments.experiment_data import ExperimentData from qiskit_experiments.base_analysis import BaseAnalysis -from qiskit_experiments.data_processing import DataProcessor +from qiskit_experiments.data_processing.data_processor import DataProcessor from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments.data_processing.processed_data import ProcessedData from .fit_result import FitResult diff --git a/qiskit_experiments/calibration/experiments/base_calibration_experiment.py b/qiskit_experiments/calibration/experiments/base_calibration_experiment.py index f0fec1ca48..d782032254 100644 --- a/qiskit_experiments/calibration/experiments/base_calibration_experiment.py +++ b/qiskit_experiments/calibration/experiments/base_calibration_experiment.py @@ -19,6 +19,7 @@ class BaseCalibrationExperiment(BaseExperiment): + """Base class for all calibration experiments.""" # Gates and pulse parameters to update __calibration_objective__ = { diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 909d10569d..23418342a8 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -23,7 +23,7 @@ from qiskit_experiments.calibration.analysis.utils import get_period_fraction from qiskit_experiments.calibration.metadata import CalibrationMetadata from qiskit_experiments.calibration.calibration_definitions import CalibrationsDefinition -from qiskit_experiments.data_processing import DataProcessor +from qiskit_experiments.data_processing.data_processor import DataProcessor from qiskit_experiments.calibration.parameter_value import ParameterValue from qiskit_experiments import ExperimentData from .base_calibration_experiment import BaseCalibrationExperiment diff --git a/qiskit_experiments/data_processing/__init__.py b/qiskit_experiments/data_processing/__init__.py index db927e91ac..fa88f2298f 100644 --- a/qiskit_experiments/data_processing/__init__.py +++ b/qiskit_experiments/data_processing/__init__.py @@ -15,8 +15,8 @@ from .base import DataAction from .nodes import ( # data acquisition node - SystemDiscriminator, - SystemKernel, + Discriminator, + Kernel, # value formatter node Population, diff --git a/qiskit_experiments/data_processing/base.py b/qiskit_experiments/data_processing/base.py index 0838d6511e..8ee0c5b467 100644 --- a/qiskit_experiments/data_processing/base.py +++ b/qiskit_experiments/data_processing/base.py @@ -16,7 +16,7 @@ from enum import Enum from typing import Any, Dict -from qiskit_experiments.calibration.exceptions import CalibrationError +from qiskit_experiments.data_processing.exceptions import DataProcessorError class DataAction(metaclass=ABCMeta): @@ -45,18 +45,18 @@ def append(self, component: 'DataAction'): component: New data processing routine. Raises: - CalibrationError: If the previous node is None (i.e. a root node) + DataProcessorError: If the previous node is None (i.e. a root node) """ if not component.prev_node: - raise CalibrationError(f'Analysis routine {component.__class__.__name__} is a root' - f'node. This routine cannot be appended to another node.') + raise DataProcessorError(f'Analysis routine {component.__class__.__name__} is a root' + f'node. This routine cannot be appended to another node.') if self._child is None: if isinstance(self, component.prev_node): self._child = component else: - raise CalibrationError(f'Analysis routine {component.__class__.__name__} ' - f'cannot be appended after {self.__class__.__name__}') + raise DataProcessorError(f'Analysis routine {component.__class__.__name__} ' + f'cannot be appended after {self.__class__.__name__}') else: self._child.append(component) diff --git a/qiskit_experiments/data_processing/data_processor.py b/qiskit_experiments/data_processing/data_processor.py index cccbc87ee8..cc126881c9 100644 --- a/qiskit_experiments/data_processing/data_processor.py +++ b/qiskit_experiments/data_processing/data_processor.py @@ -15,8 +15,8 @@ from typing import Any, Dict, Union from qiskit.qobj.utils import MeasLevel -from .nodes import SystemKernel, SystemDiscriminator -from .base import NodeType, DataAction +from qiskit_experiments.data_processing.nodes import Kernel, Discriminator +from qiskit_experiments.data_processing.base import NodeType, DataAction class DataProcessor: @@ -26,11 +26,7 @@ class DataProcessor: """ def __init__(self): - """Create an empty chain of data ProcessingSteps. - - Args: - average: Set `True` to average outcomes. - """ + """Create an empty chain of data ProcessingSteps.""" self._root_node = None def append(self, node: DataAction): @@ -53,9 +49,9 @@ def meas_level(self) -> MeasLevel: MeasLevel.RAW is returned is no kernel is defined. """ kernel = DataProcessor.check_kernel(self._root_node) - if kernel and isinstance(kernel, SystemKernel): + if kernel and isinstance(kernel, Kernel): discriminator = DataProcessor.check_discriminator(self._root_node) - if discriminator and isinstance(discriminator, SystemDiscriminator): + if discriminator and isinstance(discriminator, Discriminator): # classified level if both system kernel and discriminator are defined return MeasLevel.CLASSIFIED diff --git a/qiskit_experiments/data_processing/exceptions.py b/qiskit_experiments/data_processing/exceptions.py new file mode 100644 index 0000000000..34700c0e6e --- /dev/null +++ b/qiskit_experiments/data_processing/exceptions.py @@ -0,0 +1,28 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Exceptions for data processing.""" + +from qiskit.exceptions import QiskitError + + +class DataProcessorError(QiskitError): + """Errors raised by the calibration module.""" + + def __init__(self, *message): + """Set the error message.""" + super().__init__(*message) + self.message = ' '.join(message) + + def __str__(self): + """Return the message.""" + return repr(self.message) diff --git a/qiskit_experiments/data_processing/nodes.py b/qiskit_experiments/data_processing/nodes.py index 276b295407..aab3aea83a 100644 --- a/qiskit_experiments/data_processing/nodes.py +++ b/qiskit_experiments/data_processing/nodes.py @@ -15,8 +15,9 @@ from typing import Any, Dict, Optional import numpy as np -from qiskit_experiments.calibration.exceptions import CalibrationError -from .base import DataAction, iq_data, kernel, discriminator, prev_node, population +from qiskit_experiments.data_processing.exceptions import DataProcessorError +from qiskit_experiments.data_processing.base import (DataAction, iq_data, kernel, + discriminator, prev_node, population) @kernel @@ -24,8 +25,13 @@ class Kernel(DataAction): """User provided kernel.""" - def __init__(self, kernel, name: Optional[str] = None): - self.kernel = kernel + def __init__(self, kernel_, name: Optional[str] = None): + """ + Args: + kernel_: Kernel to kernel the data. + name: Optional name for the node. + """ + self.kernel = kernel_ self.name = name super().__init__() @@ -33,10 +39,13 @@ def process(self, data: Dict[str, Any]): """ Args: data: The data dictionary to process. + + Raises: + DataProcessorError: if the data has no memory. """ if 'memory' not in data: - raise CalibrationError(f'Data does not have memory. ' - f'Cannot apply {self.__class__.__name__}') + raise DataProcessorError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') data['memory'] = self.kernel.kernel(np.array(data['memory'])) @@ -46,13 +55,14 @@ def process(self, data: Dict[str, Any]): class Discriminator(DataAction): """Backend system discriminator.""" - def __init__(self, discriminator, name: Optional[str] = None): + def __init__(self, discriminator_, name: Optional[str] = None): """ Args: - discriminator: The discriminator used to transform the data to counts. + discriminator_: The discriminator used to transform the data to counts. For example, transform IQ data to counts. + name: Optional name for the node. """ - self.discriminator = discriminator + self.discriminator = discriminator_ self.name = name super().__init__() @@ -64,11 +74,11 @@ def process(self, data: Dict[str, Any]): data: The data in a format that can be understood by the discriminator. Raises: - CalibrationError: if the data does not contain memory. + DataProcessorError: if the data does not contain memory. """ if 'memory' not in data: - raise CalibrationError(f'Data does not have memory. ' - f'Cannot apply {self.__class__.__name__}') + raise DataProcessorError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') data['counts'] = self.discriminator.discriminate(np.array(data['memory'])) @@ -97,11 +107,11 @@ def process(self, data: Dict[str, Any]): data: The data dict. IQ data is stored under memory. Raises: - CalibrationError: if the data does not contain memory. + DataProcessorError: if the data does not contain memory. """ if 'memory' not in data: - raise CalibrationError(f'Data does not have memory. ' - f'Cannot apply {self.__class__.__name__}') + raise DataProcessorError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') # Single shot data if isinstance(data['memory'][0][0], list): @@ -140,11 +150,11 @@ def process(self, data: Dict[str, Any]): data: The data dict. IQ data is stored under memory. Raises: - CalibrationError: if the data does not contain memory. + DataProcessorError: if the data does not contain memory. """ if 'memory' not in data: - raise CalibrationError(f'Data does not have memory. ' - f'Cannot apply {self.__class__.__name__}') + raise DataProcessorError(f'Data does not have memory. ' + f'Cannot apply {self.__class__.__name__}') # Single shot data if isinstance(data['memory'][0][0], list): @@ -175,11 +185,11 @@ def process(self, data: Dict[str, Any]): populations. Raises: - CalibrationError: if counts are not in the given data. + DataProcessorError: if counts are not in the given data. """ if 'counts' not in data: - raise CalibrationError(f'Data does not have counts. ' - f'Cannot apply {self.__class__.__name__}') + raise DataProcessorError(f'Data does not have counts. ' + f'Cannot apply {self.__class__.__name__}') counts = data.get('counts') diff --git a/test/calibration/test_data_processing.py b/test/calibration/test_data_processing.py index d8a395c416..c44c47f6a0 100644 --- a/test/calibration/test_data_processing.py +++ b/test/calibration/test_data_processing.py @@ -19,11 +19,18 @@ from qiskit.qobj.common import QobjExperimentHeader from qiskit_experiments import ExperimentData from qiskit_experiments.base_experiment import BaseExperiment -from qiskit_experiments.calibration import DataProcessor -from qiskit_experiments.data_processing import (SystemKernel, SystemDiscriminator, - ToReal, ToImag, Population) +from qiskit_experiments.data_processing.data_processor import DataProcessor +from qiskit_experiments.data_processing.nodes import (Kernel, Discriminator, + ToReal, ToImag, Population) +class FakeKernel: + """Fake kernel to test the data chain.""" + + def kernel(self, data): + """Fake kernel method""" + return data + class FakeExperiment(BaseExperiment): """Fake experiment class for testing.""" @@ -96,10 +103,10 @@ def test_append_kernel(self): processor = DataProcessor() self.assertEqual(processor.meas_level(), MeasLevel.RAW) - processor.append(SystemKernel()) + processor.append(Kernel(FakeKernel)) self.assertEqual(processor.meas_level(), MeasLevel.KERNELED) - processor.append(SystemDiscriminator(None)) + processor.append(Discriminator(None)) self.assertEqual(processor.meas_level(), MeasLevel.CLASSIFIED) def test_output_key(self): @@ -107,15 +114,15 @@ def test_output_key(self): processor = DataProcessor() self.assertEqual(processor.output_key(), 'counts') - processor.append(SystemKernel()) + processor.append(Kernel(FakeKernel())) self.assertEqual(processor.output_key(), 'memory') processor.append(ToReal()) self.assertEqual(processor.output_key(), 'memory') processor = DataProcessor() - processor.append(SystemKernel()) - processor.append(SystemDiscriminator(None)) + processor.append(Kernel(FakeKernel())) + processor.append(Discriminator(None)) self.assertEqual(processor.output_key(), 'counts') processor = DataProcessor() From fa3f3eb599697eba1e4ca54c3cb51798f22b48b8 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 25 Mar 2021 11:51:09 +0100 Subject: [PATCH 32/32] * Added property and setter for calibration_objective. --- .../base_calibration_experiment.py | 21 ++++++++++++++++++- .../experiments/rough_amplitude.py | 8 +++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/qiskit_experiments/calibration/experiments/base_calibration_experiment.py b/qiskit_experiments/calibration/experiments/base_calibration_experiment.py index d782032254..41a11019bb 100644 --- a/qiskit_experiments/calibration/experiments/base_calibration_experiment.py +++ b/qiskit_experiments/calibration/experiments/base_calibration_experiment.py @@ -13,9 +13,11 @@ """Base experiment class for calibration.""" from abc import abstractmethod +from typing import Dict, List from qiskit_experiments.base_experiment import BaseExperiment from qiskit_experiments import ExperimentData +from qiskit_experiments.calibration.exceptions import CalibrationError class BaseCalibrationExperiment(BaseExperiment): @@ -25,9 +27,26 @@ class BaseCalibrationExperiment(BaseExperiment): __calibration_objective__ = { 'gates': [], 'options': [], - 'parameter_name': None + 'parameter_names': [] } + @property + def calibration_objective(self): + """Return the calibration objective of the experiment.""" + return self.__calibration_objective__ + + @calibration_objective.setter + def calibration_objective(self, objective_dict: Dict[str, List]): + """ + Args: + objective_dict: The objective dictionary specifies the gates, parameter_names + and options of the calibration. + """ + if 'parameter_names' not in objective_dict: + raise CalibrationError('The objective must specify the parameter_names.') + + self.__calibration_objective__ = dict(objective_dict) + @abstractmethod def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): """ diff --git a/qiskit_experiments/calibration/experiments/rough_amplitude.py b/qiskit_experiments/calibration/experiments/rough_amplitude.py index 23418342a8..c2abb31bcc 100644 --- a/qiskit_experiments/calibration/experiments/rough_amplitude.py +++ b/qiskit_experiments/calibration/experiments/rough_amplitude.py @@ -39,7 +39,7 @@ class RoughAmplitude(BaseCalibrationExperiment): __calibration_objective__ = { 'gates': ['x90p', 'xp'], 'options': [np.pi/2, np.pi], - 'parameter_name': ['amp_x90p', 'amp_xp'] + 'parameter_names': ['amp_x90p', 'amp_xp'] } def __init__(self, @@ -114,9 +114,9 @@ def update_calibrations(self, experiment_data: ExperimentData, index: int = -1): """ fit_result = experiment_data.analysis_result(index)['default'] - for idx, gate in enumerate(self.__calibration_objective__['gates']): - angle = self.__calibration_objective__['options'][idx] - param_name = self.__calibration_objective__['parameter_name'][idx] + for idx, gate in enumerate(self.calibration_objective['gates']): + angle = self.calibration_objective['options'][idx] + param_name = self.calibration_objective['parameter_names'][idx] amp = self._cal_def.parameter_value(param_name, DriveChannel(self._physical_qubits[0]), group=self._calibration_group)