From 3bbd770ccb60caeecdb21cc29a5462b9a38c3aeb Mon Sep 17 00:00:00 2001 From: Katie McCormick Date: Tue, 11 Aug 2026 11:50:59 -0700 Subject: [PATCH 1/6] add new QMOO tutorial --- datasets/tutorials/qmoo | 41 + docs/tutorials/_toc.json | 4 + docs/tutorials/index.mdx | 2 + ...oximate-multi-objective-optimization.ipynb | 1223 +++++++++++++++++ .../extracted-outputs/60f30d0b-1.avif | Bin 0 -> 15939 bytes .../extracted-outputs/9ccde65a-0.avif | Bin 0 -> 20136 bytes qiskit_bot.yaml | 4 + scripts/config/notebook-testing.toml | 1 + 8 files changed, 1275 insertions(+) create mode 100644 datasets/tutorials/qmoo create mode 100644 docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb create mode 100644 public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/60f30d0b-1.avif create mode 100644 public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/9ccde65a-0.avif diff --git a/datasets/tutorials/qmoo b/datasets/tutorials/qmoo new file mode 100644 index 000000000000..111dfd4e03d4 --- /dev/null +++ b/datasets/tutorials/qmoo @@ -0,0 +1,41 @@ +ticker,mu,AAPL,MSFT,NVDA,CRM,ADBE,XOM,CVX,COP,SLB,EOG,JPM,BAC,GS,MS,BLK,JNJ,UNH,PFE,ABT,TMO,PG,KO,PEP,WMT,COST,CAT,HON,UPS,GE,RTX,NEE,DUK,SO,D,AEP,AMT,PLD,CCI,EQIX,SPG +AAPL,0.272,0.0796,0.0282,0.0505,0.0281,0.0300,0.0118,0.0156,0.0207,0.0314,0.0153,0.0252,0.0304,0.0397,0.0382,0.0350,0.0024,-0.0016,0.0150,0.0071,0.0237,0.0083,0.0053,0.0087,0.0186,0.0181,0.0329,0.0242,0.0302,0.0309,0.0087,0.0066,-0.0038,-0.0023,0.0063,-0.0004,-0.0000,0.0295,0.0041,0.0160,0.0269 +MSFT,0.021,0.0282,0.0609,0.0609,0.0445,0.0331,-0.0011,0.0046,0.0071,0.0121,0.0047,0.0188,0.0186,0.0284,0.0294,0.0221,-0.0081,0.0014,0.0020,-0.0010,0.0136,-0.0041,-0.0055,-0.0053,0.0083,0.0112,0.0175,0.0113,0.0124,0.0219,0.0057,-0.0030,-0.0064,-0.0072,-0.0038,-0.0049,-0.0059,0.0110,-0.0013,0.0131,0.0120 +NVDA,0.579,0.0505,0.0609,0.2360,0.0503,0.0370,0.0033,0.0127,0.0217,0.0353,0.0156,0.0363,0.0324,0.0652,0.0634,0.0424,-0.0249,0.0003,-0.0027,-0.0078,0.0239,-0.0173,-0.0188,-0.0190,0.0056,0.0143,0.0575,0.0218,0.0128,0.0656,0.0188,-0.0024,-0.0233,-0.0236,-0.0156,-0.0193,-0.0281,0.0142,-0.0184,0.0320,0.0226 +CRM,-0.195,0.0281,0.0445,0.0503,0.1373,0.0758,0.0047,0.0108,0.0194,0.0212,0.0134,0.0246,0.0266,0.0369,0.0382,0.0264,-0.0080,0.0177,0.0081,0.0029,0.0269,-0.0046,-0.0074,-0.0039,0.0051,0.0113,0.0185,0.0138,0.0152,0.0169,0.0091,-0.0087,-0.0091,-0.0088,-0.0057,-0.0099,-0.0092,0.0122,-0.0021,0.0160,0.0212 +ADBE,-0.301,0.0300,0.0331,0.0370,0.0758,0.1186,0.0051,0.0098,0.0145,0.0195,0.0123,0.0212,0.0217,0.0290,0.0293,0.0256,-0.0038,0.0151,0.0107,0.0056,0.0259,0.0010,-0.0004,0.0017,0.0077,0.0167,0.0131,0.0167,0.0154,0.0144,0.0042,-0.0033,-0.0038,-0.0055,-0.0038,-0.0081,-0.0001,0.0204,0.0064,0.0197,0.0190 +XOM,0.186,0.0118,-0.0011,0.0033,0.0047,0.0051,0.0565,0.0457,0.0586,0.0529,0.0506,0.0139,0.0137,0.0145,0.0132,0.0115,0.0065,0.0024,0.0096,0.0033,0.0110,0.0023,0.0059,0.0079,0.0029,0.0032,0.0182,0.0112,0.0137,0.0045,0.0106,0.0137,0.0053,0.0065,0.0088,0.0066,0.0030,0.0157,0.0079,0.0034,0.0113 +CVX,0.139,0.0156,0.0046,0.0127,0.0108,0.0098,0.0457,0.0548,0.0577,0.0526,0.0497,0.0184,0.0174,0.0187,0.0190,0.0152,0.0050,0.0062,0.0116,0.0040,0.0145,0.0007,0.0039,0.0073,0.0042,0.0038,0.0211,0.0126,0.0158,0.0099,0.0104,0.0112,0.0036,0.0044,0.0086,0.0042,0.0019,0.0172,0.0066,0.0048,0.0139 +COP,0.073,0.0207,0.0071,0.0217,0.0194,0.0145,0.0586,0.0577,0.0960,0.0709,0.0721,0.0252,0.0251,0.0271,0.0284,0.0217,0.0042,0.0072,0.0129,0.0008,0.0190,-0.0020,0.0010,0.0060,0.0068,0.0037,0.0298,0.0190,0.0193,0.0130,0.0166,0.0142,0.0033,0.0041,0.0093,0.0034,0.0002,0.0207,0.0071,0.0117,0.0208 +SLB,0.160,0.0314,0.0121,0.0353,0.0212,0.0195,0.0529,0.0526,0.0709,0.1190,0.0584,0.0344,0.0351,0.0476,0.0443,0.0357,0.0055,0.0132,0.0211,0.0080,0.0352,0.0058,0.0040,0.0096,0.0102,0.0080,0.0569,0.0346,0.0370,0.0309,0.0205,0.0172,-0.0001,0.0026,0.0111,0.0020,0.0037,0.0350,0.0072,0.0140,0.0277 +EOG,0.097,0.0153,0.0047,0.0156,0.0134,0.0123,0.0506,0.0497,0.0721,0.0584,0.0752,0.0180,0.0160,0.0199,0.0202,0.0147,0.0034,0.0062,0.0099,0.0009,0.0120,-0.0019,0.0018,0.0051,0.0051,0.0053,0.0233,0.0144,0.0140,0.0109,0.0122,0.0116,0.0027,0.0039,0.0076,0.0018,0.0004,0.0189,0.0052,0.0093,0.0167 +JPM,0.252,0.0252,0.0188,0.0363,0.0246,0.0212,0.0139,0.0184,0.0252,0.0344,0.0180,0.0639,0.0504,0.0618,0.0577,0.0358,0.0031,0.0108,0.0112,0.0090,0.0219,0.0016,0.0009,0.0020,0.0145,0.0117,0.0425,0.0234,0.0230,0.0381,0.0203,0.0040,0.0014,0.0007,0.0076,0.0007,-0.0029,0.0233,0.0028,0.0140,0.0276 +BAC,0.181,0.0304,0.0186,0.0324,0.0266,0.0217,0.0137,0.0174,0.0251,0.0351,0.0160,0.0504,0.0649,0.0576,0.0588,0.0397,0.0015,0.0106,0.0136,0.0086,0.0237,0.0030,0.0000,0.0023,0.0158,0.0101,0.0397,0.0235,0.0282,0.0368,0.0156,0.0061,0.0005,0.0010,0.0087,0.0004,-0.0025,0.0284,0.0019,0.0161,0.0300 +GS,0.437,0.0397,0.0284,0.0652,0.0369,0.0290,0.0145,0.0187,0.0271,0.0476,0.0199,0.0618,0.0576,0.0941,0.0806,0.0537,0.0012,0.0105,0.0147,0.0094,0.0315,-0.0014,-0.0026,-0.0005,0.0165,0.0142,0.0608,0.0293,0.0316,0.0516,0.0241,0.0044,-0.0022,-0.0023,0.0082,-0.0024,-0.0058,0.0329,0.0013,0.0214,0.0337 +MS,0.405,0.0382,0.0294,0.0634,0.0382,0.0293,0.0132,0.0190,0.0284,0.0443,0.0202,0.0577,0.0588,0.0806,0.0894,0.0521,0.0003,0.0100,0.0138,0.0095,0.0302,-0.0029,-0.0028,-0.0010,0.0165,0.0138,0.0541,0.0265,0.0285,0.0482,0.0223,0.0047,-0.0022,-0.0033,0.0083,-0.0027,-0.0053,0.0335,0.0009,0.0203,0.0318 +BLK,0.207,0.0350,0.0221,0.0424,0.0264,0.0256,0.0115,0.0152,0.0217,0.0357,0.0147,0.0358,0.0397,0.0537,0.0521,0.0648,0.0052,0.0162,0.0158,0.0101,0.0302,0.0067,0.0019,0.0061,0.0152,0.0124,0.0430,0.0280,0.0278,0.0352,0.0158,0.0113,0.0011,0.0029,0.0108,0.0025,0.0075,0.0310,0.0117,0.0200,0.0313 +JNJ,0.254,0.0024,-0.0081,-0.0249,-0.0080,-0.0038,0.0065,0.0050,0.0042,0.0055,0.0034,0.0031,0.0015,0.0012,0.0003,0.0052,0.0320,0.0069,0.0180,0.0159,0.0116,0.0135,0.0125,0.0162,0.0086,0.0044,0.0037,0.0085,0.0087,0.0007,0.0088,0.0139,0.0131,0.0139,0.0154,0.0134,0.0165,0.0113,0.0163,0.0039,0.0071 +UNH,-0.015,-0.0016,0.0014,0.0003,0.0177,0.0151,0.0024,0.0062,0.0072,0.0132,0.0062,0.0108,0.0106,0.0105,0.0100,0.0162,0.0069,0.1792,0.0092,0.0071,0.0221,0.0050,0.0015,0.0021,0.0058,0.0071,0.0095,0.0100,0.0150,0.0036,0.0055,0.0030,0.0038,0.0058,0.0091,0.0021,0.0033,0.0076,0.0072,0.0079,0.0136 +PFE,0.039,0.0150,0.0020,-0.0027,0.0081,0.0107,0.0096,0.0116,0.0129,0.0211,0.0099,0.0112,0.0136,0.0147,0.0138,0.0158,0.0180,0.0092,0.0589,0.0153,0.0367,0.0113,0.0114,0.0173,0.0062,0.0041,0.0169,0.0186,0.0231,0.0096,0.0107,0.0148,0.0076,0.0096,0.0158,0.0096,0.0182,0.0227,0.0176,0.0113,0.0182 +ABT,-0.059,0.0071,-0.0010,-0.0078,0.0029,0.0056,0.0033,0.0040,0.0008,0.0080,0.0009,0.0090,0.0086,0.0094,0.0095,0.0101,0.0159,0.0071,0.0153,0.0504,0.0155,0.0134,0.0113,0.0115,0.0110,0.0083,0.0035,0.0135,0.0112,0.0183,0.0088,0.0139,0.0130,0.0143,0.0162,0.0132,0.0154,0.0210,0.0144,0.0102,0.0124 +TMO,-0.102,0.0237,0.0136,0.0239,0.0269,0.0259,0.0110,0.0145,0.0190,0.0352,0.0120,0.0219,0.0237,0.0315,0.0302,0.0302,0.0116,0.0221,0.0367,0.0155,0.0826,0.0092,0.0047,0.0156,0.0068,0.0062,0.0308,0.0232,0.0241,0.0225,0.0179,0.0065,-0.0009,0.0022,0.0091,0.0022,0.0077,0.0275,0.0100,0.0110,0.0227 +PG,-0.034,0.0083,-0.0041,-0.0173,-0.0046,0.0010,0.0023,0.0007,-0.0020,0.0058,-0.0019,0.0016,0.0030,-0.0014,-0.0029,0.0067,0.0135,0.0050,0.0113,0.0134,0.0092,0.0344,0.0175,0.0203,0.0149,0.0107,0.0009,0.0137,0.0143,0.0049,0.0047,0.0167,0.0125,0.0141,0.0165,0.0129,0.0178,0.0120,0.0168,0.0087,0.0117 +KO,0.165,0.0053,-0.0055,-0.0188,-0.0074,-0.0004,0.0059,0.0039,0.0010,0.0040,0.0018,0.0009,0.0000,-0.0026,-0.0028,0.0019,0.0125,0.0015,0.0114,0.0113,0.0047,0.0175,0.0268,0.0199,0.0110,0.0086,-0.0044,0.0080,0.0046,0.0037,0.0063,0.0162,0.0133,0.0135,0.0154,0.0132,0.0170,0.0093,0.0165,0.0070,0.0084 +PEP,-0.037,0.0087,-0.0053,-0.0190,-0.0039,0.0017,0.0079,0.0073,0.0060,0.0096,0.0051,0.0020,0.0023,-0.0005,-0.0010,0.0061,0.0162,0.0021,0.0173,0.0115,0.0156,0.0203,0.0199,0.0428,0.0113,0.0081,0.0034,0.0114,0.0136,-0.0008,0.0045,0.0200,0.0131,0.0147,0.0181,0.0135,0.0191,0.0137,0.0195,0.0073,0.0106 +WMT,0.438,0.0186,0.0083,0.0056,0.0051,0.0077,0.0029,0.0042,0.0068,0.0102,0.0051,0.0145,0.0158,0.0165,0.0165,0.0152,0.0086,0.0058,0.0062,0.0110,0.0068,0.0149,0.0110,0.0113,0.0554,0.0288,0.0155,0.0166,0.0122,0.0187,0.0096,0.0111,0.0088,0.0096,0.0134,0.0095,0.0114,0.0182,0.0117,0.0175,0.0164 +COST,0.167,0.0181,0.0112,0.0143,0.0113,0.0167,0.0032,0.0038,0.0037,0.0080,0.0053,0.0117,0.0101,0.0142,0.0138,0.0124,0.0044,0.0071,0.0041,0.0083,0.0062,0.0107,0.0086,0.0081,0.0288,0.0410,0.0080,0.0137,0.0055,0.0161,0.0065,0.0063,0.0057,0.0055,0.0074,0.0052,0.0060,0.0120,0.0062,0.0144,0.0147 +CAT,0.538,0.0329,0.0175,0.0575,0.0185,0.0131,0.0182,0.0211,0.0298,0.0569,0.0233,0.0425,0.0397,0.0608,0.0541,0.0430,0.0037,0.0095,0.0169,0.0035,0.0308,0.0009,-0.0044,0.0034,0.0155,0.0080,0.1038,0.0319,0.0430,0.0490,0.0230,0.0118,-0.0027,-0.0005,0.0087,0.0057,-0.0019,0.0345,0.0039,0.0213,0.0266 +HON,0.108,0.0242,0.0113,0.0218,0.0138,0.0167,0.0112,0.0126,0.0190,0.0346,0.0144,0.0234,0.0235,0.0293,0.0265,0.0280,0.0085,0.0100,0.0186,0.0135,0.0232,0.0137,0.0080,0.0114,0.0166,0.0137,0.0319,0.0536,0.0244,0.0300,0.0199,0.0147,0.0060,0.0083,0.0133,0.0066,0.0121,0.0239,0.0128,0.0164,0.0229 +UPS,-0.105,0.0302,0.0124,0.0128,0.0152,0.0154,0.0137,0.0158,0.0193,0.0370,0.0140,0.0230,0.0282,0.0316,0.0285,0.0278,0.0087,0.0150,0.0231,0.0112,0.0241,0.0143,0.0046,0.0136,0.0122,0.0055,0.0430,0.0244,0.0932,0.0134,0.0075,0.0122,0.0013,0.0036,0.0146,0.0057,0.0094,0.0356,0.0083,0.0118,0.0251 +GE,0.351,0.0309,0.0219,0.0656,0.0169,0.0144,0.0045,0.0099,0.0130,0.0309,0.0109,0.0381,0.0368,0.0516,0.0482,0.0352,0.0007,0.0036,0.0096,0.0183,0.0225,0.0049,0.0037,-0.0008,0.0187,0.0161,0.0490,0.0300,0.0134,0.1051,0.0368,0.0109,0.0012,0.0020,0.0083,0.0048,0.0006,0.0274,0.0053,0.0249,0.0292 +RTX,0.308,0.0087,0.0057,0.0188,0.0091,0.0042,0.0106,0.0104,0.0166,0.0205,0.0122,0.0203,0.0156,0.0241,0.0223,0.0158,0.0088,0.0055,0.0107,0.0088,0.0179,0.0047,0.0063,0.0045,0.0096,0.0065,0.0230,0.0199,0.0075,0.0368,0.0615,0.0077,0.0066,0.0063,0.0120,0.0073,0.0078,0.0152,0.0091,0.0137,0.0161 +NEE,0.173,0.0066,-0.0030,-0.0024,-0.0087,-0.0033,0.0137,0.0112,0.0142,0.0172,0.0116,0.0040,0.0061,0.0044,0.0047,0.0113,0.0139,0.0030,0.0148,0.0139,0.0065,0.0167,0.0162,0.0200,0.0111,0.0063,0.0118,0.0147,0.0122,0.0109,0.0077,0.0701,0.0231,0.0254,0.0318,0.0250,0.0281,0.0200,0.0283,0.0163,0.0127 +DUK,0.143,-0.0038,-0.0064,-0.0233,-0.0091,-0.0038,0.0053,0.0036,0.0033,-0.0001,0.0027,0.0014,0.0005,-0.0022,-0.0022,0.0011,0.0131,0.0038,0.0076,0.0130,-0.0009,0.0125,0.0133,0.0131,0.0088,0.0057,-0.0027,0.0060,0.0013,0.0012,0.0066,0.0231,0.0270,0.0240,0.0250,0.0228,0.0221,0.0103,0.0215,0.0077,0.0072 +SO,0.133,-0.0023,-0.0072,-0.0236,-0.0088,-0.0055,0.0065,0.0044,0.0041,0.0026,0.0039,0.0007,0.0010,-0.0023,-0.0033,0.0029,0.0139,0.0058,0.0096,0.0143,0.0022,0.0141,0.0135,0.0147,0.0096,0.0055,-0.0005,0.0083,0.0036,0.0020,0.0063,0.0254,0.0240,0.0304,0.0271,0.0241,0.0231,0.0111,0.0215,0.0092,0.0074 +D,0.154,0.0063,-0.0038,-0.0156,-0.0057,-0.0038,0.0088,0.0086,0.0093,0.0111,0.0076,0.0076,0.0087,0.0082,0.0083,0.0108,0.0154,0.0091,0.0158,0.0162,0.0091,0.0165,0.0154,0.0181,0.0134,0.0074,0.0087,0.0133,0.0146,0.0083,0.0120,0.0318,0.0250,0.0271,0.0445,0.0257,0.0257,0.0211,0.0267,0.0124,0.0160 +AEP,0.221,-0.0004,-0.0049,-0.0193,-0.0099,-0.0081,0.0066,0.0042,0.0034,0.0020,0.0018,0.0007,0.0004,-0.0024,-0.0027,0.0025,0.0134,0.0021,0.0096,0.0132,0.0022,0.0129,0.0132,0.0135,0.0095,0.0052,0.0057,0.0066,0.0057,0.0048,0.0073,0.0250,0.0228,0.0241,0.0257,0.0348,0.0235,0.0118,0.0230,0.0092,0.0076 +AMT,0.007,-0.0000,-0.0059,-0.0281,-0.0092,-0.0001,0.0030,0.0019,0.0002,0.0037,0.0004,-0.0029,-0.0025,-0.0058,-0.0053,0.0075,0.0165,0.0033,0.0182,0.0154,0.0077,0.0178,0.0170,0.0191,0.0114,0.0060,-0.0019,0.0121,0.0094,0.0006,0.0078,0.0281,0.0221,0.0231,0.0257,0.0235,0.0646,0.0201,0.0534,0.0189,0.0116 +PLD,0.198,0.0295,0.0110,0.0142,0.0122,0.0204,0.0157,0.0172,0.0207,0.0350,0.0189,0.0233,0.0284,0.0329,0.0335,0.0310,0.0113,0.0076,0.0227,0.0210,0.0275,0.0120,0.0093,0.0137,0.0182,0.0120,0.0345,0.0239,0.0356,0.0274,0.0152,0.0200,0.0103,0.0111,0.0211,0.0118,0.0201,0.0656,0.0207,0.0273,0.0389 +CCI,0.003,0.0041,-0.0013,-0.0184,-0.0021,0.0064,0.0079,0.0066,0.0071,0.0072,0.0052,0.0028,0.0019,0.0013,0.0009,0.0117,0.0163,0.0072,0.0176,0.0144,0.0100,0.0168,0.0165,0.0195,0.0117,0.0062,0.0039,0.0128,0.0083,0.0053,0.0091,0.0283,0.0215,0.0215,0.0267,0.0230,0.0534,0.0207,0.0688,0.0170,0.0138 +EQIX,0.198,0.0160,0.0131,0.0320,0.0160,0.0197,0.0034,0.0048,0.0117,0.0140,0.0093,0.0140,0.0161,0.0214,0.0203,0.0200,0.0039,0.0079,0.0113,0.0102,0.0110,0.0087,0.0070,0.0073,0.0175,0.0144,0.0213,0.0164,0.0118,0.0249,0.0137,0.0163,0.0077,0.0092,0.0124,0.0092,0.0189,0.0273,0.0170,0.0630,0.0234 +SPG,0.228,0.0269,0.0120,0.0226,0.0212,0.0190,0.0113,0.0139,0.0208,0.0277,0.0167,0.0276,0.0300,0.0337,0.0318,0.0313,0.0071,0.0136,0.0182,0.0124,0.0227,0.0117,0.0084,0.0106,0.0164,0.0147,0.0266,0.0229,0.0251,0.0292,0.0161,0.0127,0.0072,0.0074,0.0160,0.0076,0.0116,0.0389,0.0138,0.0234,0.0550 diff --git a/docs/tutorials/_toc.json b/docs/tutorials/_toc.json index f48a28605b09..175b34709cdf 100644 --- a/docs/tutorials/_toc.json +++ b/docs/tutorials/_toc.json @@ -40,6 +40,10 @@ "title": "Advanced techniques for QAOA", "url": "/docs/tutorials/advanced-techniques-for-qaoa" }, + { + "title": "Quantum approximate multi-objective optimization", + "url": "/docs/tutorials/quantum-approximate-multi-objective-optimization" + }, { "title": "Pauli correlation encoding to reduce Max-Cut requirements", "url": "/docs/tutorials/pauli-correlation-encoding-for-qaoa" diff --git a/docs/tutorials/index.mdx b/docs/tutorials/index.mdx index 0fec3485b44a..6921ea67e493 100644 --- a/docs/tutorials/index.mdx +++ b/docs/tutorials/index.mdx @@ -37,6 +37,8 @@ The tutorials highlight techniques where repeated sampling enables estimation of * [Advanced techniques for QAOA](/docs/tutorials/advanced-techniques-for-qaoa) +* [Quantum approximate multi-objective optimization](/docs/tutorials/quantum-approximate-multi-objective-optimization) + * [Pauli correlation encoding to reduce max-cut requirements](/docs/tutorials/pauli-correlation-encoding-for-qaoa) diff --git a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb new file mode 100644 index 000000000000..2b04c3be55ca --- /dev/null +++ b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb @@ -0,0 +1,1223 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "243d2f90", + "metadata": {}, + "source": [ + "---\n", + "title: Quantum approximate multi-objective optimization\n", + "description: Use a QAOA sampler to trace the risk/return/diversification Pareto front of a cardinality-constrained portfolio.\n", + "---\n", + "\n", + "# Quantum approximate multi-objective optimization\n", + "*Usage estimate: about 10 minutes on an IBM Heron processor.*\n", + "\n", + "{/* cspell:ignore maximise, Kotil, moocore, hypervolume, binom, Dicke, f'Hypervolume, lightgray, steelblue, Sparsify, sparsification, sparsified, pmap */}" + ] + }, + { + "cell_type": "markdown", + "id": "9d322795", + "metadata": { + "tags": [ + "version-info" + ] + }, + "source": [ + "{/* This cell is auto-populated with package versions when the notebook is executed. */}" + ] + }, + { + "cell_type": "markdown", + "id": "e861cf7f", + "metadata": {}, + "source": [ + "## Learning outcomes\n", + "This tutorial solves a cardinality-constrained portfolio optimization problem: Given the constraint of holding exactly $K$ assets, balance risk, return, and diversification objectives to find the set of optimal portfolios.\n", + "\n", + "After completing this tutorial, you can expect to understand:\n", + "\n", + "- How to express a portfolio-selection problem with three competing objectives — low risk, high return, and good diversification — as a quantum optimization problem.\n", + "- How a single QAOA circuit, swept over a set of objective weights, traces out a *Pareto front* of optimal trade-off portfolios.\n", + "- How an XY mixer enforces a \"choose exactly K assets\" constraint for free, so no penalty term is needed.\n", + "- How to train the circuit's angles with a matrix-product-state simulator at a scale too large to optimize exactly, following Kotil et al. (arXiv:2503.22797).\n", + "\n", + "## Prerequisites\n", + "It is recommended that you are familiar with:\n", + "\n", + "- The [Qiskit patterns](/docs/guides/intro-to-patterns) workflow (map, optimize, execute, post-process).\n", + "- The basics of [QAOA](/docs/tutorials/quantum-approximate-optimization-algorithm).\n", + "\n", + "## Background\n", + "A portfolio manager rarely optimizes a single number. They want returns to be high, risk (the variance of those returns) to be low, and the holdings spread across sectors so the portfolio is not over-exposed to any one part of the market. These goals pull against each other: the highest-return assets are often the most volatile, and concentrating in one hot sector hurts diversification.\n", + "\n", + "There is no single \"best\" portfolio. Instead there is a *Pareto front*: the set of portfolios for which you cannot improve one objective without giving up another. Our goal is to map out that front so a decision-maker can pick the trade-off they prefer.\n", + "\n", + "We pose the problem as choosing exactly $K$ assets out of $N$ (each asset is either in or out — one qubit per asset). Three Hamiltonians encode the three objectives. We combine them with weights $c$ that live on a simplex (they sum to one), and a QAOA sampler returns good portfolios for each weight choice. Sweeping the weights sweeps the relative importance of risk vs. return vs. diversification, and the union of all the sampled portfolios traces the Pareto front.\n", + "\n", + "We first walk through the whole workflow on a small 8-asset example we can verify by brute force, then run the identical method on a 40-asset instance sized for quantum hardware." + ] + }, + { + "cell_type": "markdown", + "id": "ce005248", + "metadata": {}, + "source": [ + "## Requirements\n", + "Before starting this tutorial, be sure you have the following installed:\n", + "\n", + "- Qiskit SDK v2.0 or later, with visualization support\n", + "- Qiskit Runtime v0.22 or later (`pip install qiskit-ibm-runtime`)\n", + "- Qiskit Aer (`pip install qiskit-aer`)\n", + "- The optimization mapper and training pipeline (`qiskit-addon-opt-mapper`, `qaoa-training-pipeline`)\n", + "- `moocore` for Pareto-front and hypervolume calculations (`pip install moocore`)" + ] + }, + { + "cell_type": "markdown", + "id": "df0b2a73", + "metadata": {}, + "source": [ + "## Setup\n", + "Import the libraries used throughout the tutorial and fix a random seed for reproducibility." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "f2437893", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:38.564587Z", + "iopub.status.busy": "2026-06-15T20:17:38.564309Z", + "iopub.status.idle": "2026-06-15T20:17:40.849209Z", + "shell.execute_reply": "2026-06-15T20:17:40.848675Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Setup complete.\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from math import comb\n", + "from moocore import hypervolume, filter_dominated, is_nondominated\n", + "\n", + "from qiskit import QuantumCircuit\n", + "from qiskit.circuit import ParameterVector\n", + "from qiskit.circuit.library import qaoa_ansatz\n", + "from qiskit.quantum_info import SparsePauliOp\n", + "from qiskit.transpiler import generate_preset_pass_manager\n", + "from qiskit_aer.primitives import SamplerV2 as AerSampler\n", + "from qiskit_addon_opt_mapper.problems import OptimizationProblem\n", + "from qaoa_training_pipeline.training import ScipyTrainer\n", + "from qaoa_training_pipeline.evaluation import (\n", + " StatevectorEvaluator,\n", + " MPSAerEvaluator,\n", + ")\n", + "\n", + "np.random.seed(42)\n", + "sampler = AerSampler(seed=42) # local simulator for the small-scale example\n", + "print(\"Setup complete.\")" + ] + }, + { + "cell_type": "markdown", + "id": "13d7a938", + "metadata": {}, + "source": [ + "## Small-scale simulator example\n", + "We start with 8 assets drawn from 6 sectors and select exactly $K=4$ of them. With only 8 assets there are just $\\binom{8}{4}=70$ valid portfolios, so we can later check the quantum result against an exhaustive search." + ] + }, + { + "cell_type": "markdown", + "id": "00ffb9a5", + "metadata": {}, + "source": [ + "### Step 1: Map classical inputs to a quantum problem\n", + "Each asset is one qubit; a bitstring like `10110010` is a portfolio (the 1s are the assets we hold). We need three ingredients: the market data, the three objective Hamiltonians, and a circuit that only ever proposes portfolios with exactly $K$ assets." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "7d77a670", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:40.850280Z", + "iopub.status.busy": "2026-06-15T20:17:40.850100Z", + "iopub.status.idle": "2026-06-15T20:17:40.854431Z", + "shell.execute_reply": "2026-06-15T20:17:40.853895Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8 assets, choose K=4, 3 objectives\n", + " AAPL (Tech ) expected return 28%\n", + " XOM (Energy ) expected return 12%\n", + " JPM (Finance ) expected return 22%\n", + " JNJ (Health ) expected return 5%\n", + " KO (Staples ) expected return 8%\n", + " AMT (REIT ) expected return 10%\n", + " AMZN (Tech ) expected return 32%\n", + " SLB (Energy ) expected return 15%\n" + ] + } + ], + "source": [ + "# --- Small-scale universe: 8 assets across 6 sectors ---\n", + "tickers = [\"AAPL\", \"XOM\", \"JPM\", \"JNJ\", \"KO\", \"AMT\", \"AMZN\", \"SLB\"]\n", + "sectors = [\n", + " \"Tech\",\n", + " \"Energy\",\n", + " \"Finance\",\n", + " \"Health\",\n", + " \"Staples\",\n", + " \"REIT\",\n", + " \"Tech\",\n", + " \"Energy\",\n", + "]\n", + "n_assets = len(tickers)\n", + "K = 4 # choose exactly K assets\n", + "n_obj = 3 # risk, return, diversification\n", + "\n", + "# Annualized expected returns\n", + "mu = np.array([0.28, 0.12, 0.22, 0.05, 0.08, 0.10, 0.32, 0.15])\n", + "\n", + "# Annualized covariance matrix (the \"risk\" model)\n", + "sigma = np.array(\n", + " [\n", + " [0.070, 0.010, 0.020, 0.008, 0.005, 0.012, 0.045, 0.011],\n", + " [0.010, 0.065, 0.015, 0.006, 0.004, 0.008, 0.009, 0.050],\n", + " [0.020, 0.015, 0.055, 0.010, 0.007, 0.015, 0.018, 0.014],\n", + " [0.008, 0.006, 0.010, 0.030, 0.012, 0.009, 0.007, 0.005],\n", + " [0.005, 0.004, 0.007, 0.012, 0.025, 0.006, 0.004, 0.003],\n", + " [0.012, 0.008, 0.015, 0.009, 0.006, 0.045, 0.011, 0.007],\n", + " [0.045, 0.009, 0.018, 0.007, 0.004, 0.011, 0.085, 0.010],\n", + " [0.011, 0.050, 0.014, 0.005, 0.003, 0.007, 0.010, 0.072],\n", + " ]\n", + ")\n", + "\n", + "# Diversification score: number of cross-sector pairs in the portfolio.\n", + "# D[i,j] = 0.5 when assets i and j are in different sectors, so x^T D x counts\n", + "# the cross-sector pairs. More cross-sector pairs = better diversified.\n", + "D = np.array(\n", + " [\n", + " [1.0 if sectors[i] != sectors[j] else 0.0 for j in range(n_assets)]\n", + " for i in range(n_assets)\n", + " ]\n", + ")\n", + "np.fill_diagonal(D, 0.0)\n", + "D = D / 2\n", + "\n", + "print(f\"{n_assets} assets, choose K={K}, {n_obj} objectives\")\n", + "for t, s, m in zip(tickers, sectors, mu):\n", + " print(f\" {t:5s} ({s:8s}) expected return {m:5.0%}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "b8ad09db", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:40.855265Z", + "iopub.status.busy": "2026-06-15T20:17:40.855190Z", + "iopub.status.idle": "2026-06-15T20:17:40.871104Z", + "shell.execute_reply": "2026-06-15T20:17:40.870504Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "H_risk : 36 Pauli terms\n", + "H_return : 8 Pauli terms\n", + "H_diversity : 34 Pauli terms\n" + ] + } + ], + "source": [ + "# Each objective becomes a Hamiltonian whose lowest-energy bitstrings are the\n", + "# best portfolios for that objective. The opt-mapper turns a plain\n", + "# min/max problem over binary variables into the equivalent Ising operator.\n", + "def build_risk_hamiltonian(sigma, n):\n", + " \"\"\"Minimize portfolio variance x^T sigma x (quadratic -> ZZ terms).\"\"\"\n", + " prob = OptimizationProblem(\"risk\")\n", + " prob.binary_var_list(n)\n", + " prob.minimize(quadratic=sigma)\n", + " op, _ = prob.to_ising()\n", + " return op.simplify()\n", + "\n", + "\n", + "def build_return_hamiltonian(mu, n):\n", + " \"\"\"Maximize expected return mu . x (linear -> Z terms).\"\"\"\n", + " prob = OptimizationProblem(\"return\")\n", + " prob.binary_var_list(n)\n", + " prob.maximize(linear=mu)\n", + " op, _ = prob.to_ising()\n", + " return op.simplify()\n", + "\n", + "\n", + "def build_diversity_hamiltonian(D, n):\n", + " \"\"\"Maximize cross-sector pairs x^T D x (quadratic -> ZZ terms).\"\"\"\n", + " prob = OptimizationProblem(\"diversity\")\n", + " prob.binary_var_list(n)\n", + " prob.maximize(quadratic=D)\n", + " op, _ = prob.to_ising()\n", + " return op.simplify()\n", + "\n", + "\n", + "H_risk = build_risk_hamiltonian(sigma, n_assets)\n", + "H_return = build_return_hamiltonian(mu, n_assets)\n", + "H_diversity = build_diversity_hamiltonian(D, n_assets)\n", + "cost_ops = [H_risk, H_return, H_diversity]\n", + "\n", + "for name, op in zip([\"risk\", \"return\", \"diversity\"], cost_ops):\n", + " print(f\"H_{name:10s}: {op.size} Pauli terms\")" + ] + }, + { + "cell_type": "markdown", + "id": "6d09b554", + "metadata": {}, + "source": [ + "**Enforcing \"exactly K assets\" without a penalty.** A common trick is to add a penalty term that punishes portfolios of the wrong size, but that couples every qubit to every other qubit — an all-to-all circuit that does not fit on hardware. Instead we use an **XY mixer**, which only ever moves the QAOA state *between* bitstrings of the same Hamming weight. If we start in a state that already has $K$ assets selected, every portfolio the circuit explores also has exactly $K$ assets. The constraint is built into the circuit's structure, for free.\n", + "\n", + "We prepare the starting state cheaply: rotate each qubit so it is \"on\" with probability $K/N$. Restricted to the $K$-asset outcomes, this reproduces the ideal equal-weight (Dicke) state, so we simply keep the measured bitstrings that have exactly $K$ ones — a step called *post-selection*." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "502dee54", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:40.872045Z", + "iopub.status.busy": "2026-06-15T20:17:40.871967Z", + "iopub.status.idle": "2026-06-15T20:17:41.745811Z", + "shell.execute_reply": "2026-06-15T20:17:41.745276Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Qubits: 8 | QAOA layers: 1\n", + "Tunable angles: 1 beta + 1 gamma, plus 3 objective weights\n" + ] + } + ], + "source": [ + "def xy_mixer(n):\n", + " \"\"\"Line XY mixer: couples neighboring qubits with XX+YY. Conserves the number\n", + " of selected assets (Hamming weight), so cardinality is preserved automatically.\n", + " Using a line (not a full ring) keeps the circuit shallow and hardware-friendly.\"\"\"\n", + " terms = [\n", + " (pauli, [i, i + 1], 1) for i in range(n - 1) for pauli in (\"XX\", \"YY\")\n", + " ]\n", + " return SparsePauliOp.from_sparse_list(terms, n)\n", + "\n", + "\n", + "def product_init(n, k):\n", + " \"\"\"Cheap initial state: each qubit rotated so P(selected) = k/n. Zero two-qubit\n", + " gates. Post-selecting its weight-k outcomes reproduces the ideal Dicke state.\"\"\"\n", + " qc = QuantumCircuit(n)\n", + " theta = 2 * np.arcsin(np.sqrt(k / n))\n", + " for q in range(n):\n", + " qc.ry(theta, q)\n", + " return qc\n", + "\n", + "\n", + "# Combine the three objectives with weights c (bound later, at sampling time).\n", + "p_layers = 1\n", + "c = ParameterVector(\"c\", n_obj)\n", + "combined_cost_op = sum(\n", + " c[k] * H_k for k, H_k in enumerate(cost_ops)\n", + ").simplify()\n", + "\n", + "ansatz = qaoa_ansatz(\n", + " combined_cost_op,\n", + " reps=p_layers,\n", + " initial_state=product_init(n_assets, K),\n", + " mixer_operator=xy_mixer(n_assets),\n", + ")\n", + "ansatz.measure_all()\n", + "\n", + "betas = [p for p in ansatz.parameters if p.name.startswith(\"β\")]\n", + "gammas = [p for p in ansatz.parameters if p.name.startswith(\"γ\")]\n", + "print(f\"Qubits: {ansatz.num_qubits} | QAOA layers: {p_layers}\")\n", + "print(\n", + " f\"Tunable angles: {len(betas)} beta + {len(gammas)} gamma, plus {n_obj} objective weights\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "878b5a3c", + "metadata": {}, + "source": [ + "### Step 2: Optimize problem for quantum hardware execution\n", + "Before running, the abstract circuit is transpiled into hardware-native gates. At this small scale we just inspect the cost: how deep is the circuit and how many two-qubit gates does it use? (Two-qubit gates are the main source of noise on real devices.)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "101570a7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:41.746926Z", + "iopub.status.busy": "2026-06-15T20:17:41.746776Z", + "iopub.status.idle": "2026-06-15T20:17:41.771048Z", + "shell.execute_reply": "2026-06-15T20:17:41.770523Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Circuit depth : 25\n", + "Two-qubit gate depth : 23\n", + "Two-qubit gate count : 42\n" + ] + } + ], + "source": [ + "# Bind dummy angle values so we can transpile and measure the circuit's size.\n", + "dummy = {p: 0.1 for p in ansatz.parameters}\n", + "test_pm = generate_preset_pass_manager(optimization_level=1)\n", + "test_qc = test_pm.run(ansatz.assign_parameters(dummy))\n", + "\n", + "print(f\"Circuit depth : {test_qc.depth()}\")\n", + "print(\n", + " f\"Two-qubit gate depth : {test_qc.depth(lambda x: len(x.qubits) > 1)}\"\n", + ")\n", + "print(f\"Two-qubit gate count : {test_qc.num_nonlocal_gates()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9e463671", + "metadata": {}, + "source": [ + "### Step 3: Execute using Qiskit primitives\n", + "Two stages. First we **train** the QAOA angles once, using equal objective weights, with an exact statevector simulator — this finds good $\\beta,\\gamma$ values. Then we **sweep** many weight vectors across the simplex and sample the circuit at each, collecting candidate portfolios. Because only the objective weights change between sweeps (not the trained angles), all the weight vectors are submitted in a single batched job." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "930096a4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:41.772130Z", + "iopub.status.busy": "2026-06-15T20:17:41.772053Z", + "iopub.status.idle": "2026-06-15T20:17:43.786111Z", + "shell.execute_reply": "2026-06-15T20:17:43.785568Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training QAOA angles (exact statevector)...\n", + "Trained beta : [3.329186967386619]\n", + "Trained gamma: [3.4449804324291033]\n" + ] + } + ], + "source": [ + "# Train the angles with equal objective weights.\n", + "# The trainer maximizes energy, so we negate the (to-be-minimized) objective sum.\n", + "training_op = sum(-1.0 / n_obj * H_k for H_k in cost_ops).simplify()\n", + "\n", + "# Linear-ramp initialization (Zhou et al., arXiv:2101.05742)\n", + "dt = 0.75\n", + "grid = np.arange(1, p_layers + 1) - 0.5\n", + "init_params = np.concatenate((1 - grid * dt / p_layers, grid * dt / p_layers))\n", + "\n", + "trainer = ScipyTrainer(\n", + " StatevectorEvaluator(), minimize_args={\"options\": {\"maxiter\": 300}}\n", + ")\n", + "print(\"Training QAOA angles (exact statevector)...\")\n", + "result_train = trainer.train(\n", + " cost_op=training_op,\n", + " mixer=xy_mixer(n_assets),\n", + " initial_state=product_init(n_assets, K),\n", + " params0=init_params,\n", + ")\n", + "opt = result_train[\"optimized_params\"]\n", + "opt_betas, opt_gammas = opt[:p_layers], opt[p_layers:]\n", + "print(f\"Trained beta : {opt_betas}\")\n", + "print(f\"Trained gamma: {opt_gammas}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "dca238dc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:43.787054Z", + "iopub.status.busy": "2026-06-15T20:17:43.786979Z", + "iopub.status.idle": "2026-06-15T20:17:44.015930Z", + "shell.execute_reply": "2026-06-15T20:17:44.015455Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sampling 200 weight vectors x 500 shots...\n", + "Distinct portfolios sampled: 256\n" + ] + } + ], + "source": [ + "def random_uniform_simplex(n_samples, n_obj=3):\n", + " \"\"\"n_samples weight vectors spread uniformly over the (n_obj-1)-simplex.\"\"\"\n", + " s = np.zeros((n_samples, n_obj + 1))\n", + " s[:, 1:-1] = np.random.rand(n_samples, n_obj - 1)\n", + " s[:, -1] = 1\n", + " s = np.sort(s, axis=1)\n", + " return np.diff(s, axis=1)\n", + "\n", + "\n", + "# Bind the trained angles, leaving the objective weights c free for the sweep.\n", + "param_map = {betas[i]: opt_betas[i] for i in range(p_layers)}\n", + "param_map.update({gammas[i]: opt_gammas[i] for i in range(p_layers)})\n", + "ansatz_bound = ansatz.assign_parameters(param_map)\n", + "\n", + "n_samples, shots = 200, 500\n", + "c_vecs = random_uniform_simplex(n_samples, n_obj)\n", + "\n", + "print(f\"Sampling {n_samples} weight vectors x {shots} shots...\")\n", + "result = sampler.run([(ansatz_bound, c_vecs)], shots=shots).result()\n", + "\n", + "# Collect every distinct bitstring seen across all weight vectors.\n", + "all_bitstrings = set()\n", + "for s in range(n_samples):\n", + " for bs in result[0].data.meas.get_counts(s):\n", + " all_bitstrings.add(bs.replace(\" \", \"\"))\n", + "print(f\"Distinct portfolios sampled: {len(all_bitstrings)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0de47400", + "metadata": {}, + "source": [ + "### Step 4: Post-process and return result in desired classical format\n", + "We keep only the feasible portfolios (exactly $K$ assets — the post-selection step), score each one on all three objectives, and extract the **Pareto front**: the portfolios that are not beaten on every objective at once. The *hypervolume* is a single number summarizing how much objective space the front dominates — bigger is better." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "224cc584", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:44.016909Z", + "iopub.status.busy": "2026-06-15T20:17:44.016831Z", + "iopub.status.idle": "2026-06-15T20:17:44.020916Z", + "shell.execute_reply": "2026-06-15T20:17:44.020533Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feasible portfolios found : 70 of 70 possible\n", + "Pareto-front portfolios : 26\n", + "Hypervolume : 0.2487\n" + ] + } + ], + "source": [ + "def evaluate_portfolio(bitstring, sigma, mu, D):\n", + " \"\"\"Score one portfolio on all three objectives (all framed as 'bigger is better').\"\"\"\n", + " x = np.array([int(b) for b in bitstring])\n", + " return np.array(\n", + " [\n", + " -(x @ sigma @ x), # negative risk\n", + " x @ mu, # return\n", + " x @ D @ x,\n", + " ]\n", + " ) # diversification (cross-sector pairs)\n", + "\n", + "\n", + "# Post-select feasible portfolios, then score them.\n", + "feasible = [bs for bs in all_bitstrings if bs.count(\"1\") == K]\n", + "fis = np.array([evaluate_portfolio(bs, sigma, mu, D) for bs in feasible])\n", + "\n", + "pareto_front = filter_dominated(fis, maximise=True)\n", + "ref_point = fis.min(axis=0)\n", + "qmoo_hv = hypervolume(fis, ref=ref_point, maximise=True)\n", + "\n", + "print(\n", + " f\"Feasible portfolios found : {len(feasible)} of {comb(n_assets, K)} possible\"\n", + ")\n", + "print(f\"Pareto-front portfolios : {len(pareto_front)}\")\n", + "print(f\"Hypervolume : {qmoo_hv:.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "9ccde65a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-06-15T20:17:44.021899Z", + "iopub.status.busy": "2026-06-15T20:17:44.021827Z", + "iopub.status.idle": "2026-06-15T20:17:44.154770Z", + "shell.execute_reply": "2026-06-15T20:17:44.154085Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig = plt.figure(figsize=(8, 6))\n", + "ax = fig.add_subplot(111, projection=\"3d\")\n", + "ax.scatter(\n", + " fis[:, 0],\n", + " fis[:, 1],\n", + " fis[:, 2],\n", + " c=\"lightgray\",\n", + " s=12,\n", + " label=\"All feasible portfolios\",\n", + ")\n", + "ax.scatter(\n", + " pareto_front[:, 0],\n", + " pareto_front[:, 1],\n", + " pareto_front[:, 2],\n", + " c=\"steelblue\",\n", + " s=45,\n", + " label=\"Pareto front\",\n", + ")\n", + "ax.set_xlabel(\"Negative risk\")\n", + "ax.set_ylabel(\"Return\")\n", + "ax.set_zlabel(\"Diversification\")\n", + "ax.set_title(\"Risk / return / diversification Pareto front (8 assets)\")\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "202734e8", + "metadata": {}, + "source": [ + "## Large-scale hardware example\n", + "Now the same workflow on **40 assets** (8 sectors × 5), choosing $K=6$. Forty qubits is too large to optimize the angles exactly ($2^{40}$ amplitudes) and too large to verify by brute force, and a dense circuit would be too deep for current hardware. Several things change, and **nothing else about the method does**:\n", + "\n", + "1. **Train the angles with a matrix-product-state (MPS) simulator, not exact statevector.** Following the reference (Kotil et al.), we fix the objective weights to equal values, optimize a single β, γ on the MPS simulator, and reuse them for every weighting vector in the sweep. (We train at the size we run — no small-to-large angle transfer.)\n", + "2. **Sparsify the risk model to fit hardware.** A full covariance couples all 780 asset pairs. We keep only the strongest, cheapest-to-route couplings using *importance-aware QAP truncation*, and couple each sector in a light ring for the diversity term — together this keeps the objectives meaningful while holding the circuit to a hardware-friendly size.\n", + "3. **Keep the circuit shallow and score honestly.** Routing is stochastic, so we transpile with several seeds and keep the shallowest (free — no quantum time spent). Portfolios are always **scored against the true, full objectives** — the sparsification only shapes the circuit, not how portfolios are judged.\n", + "\n", + "> **Why QAP truncation?** Keeping each asset's largest couplings by magnitude alone can leave a circuit that is sparse but still awkward to route. QAP truncation instead keeps couplings that are both large *and* physically close on the chip, so the same gate budget buys a shallower, more hardware-friendly circuit." + ] + }, + { + "cell_type": "markdown", + "id": "d5b3ad56", + "metadata": {}, + "source": [ + "### Step 1: Map inputs (sparsified for hardware)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65ac8a46", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [], + "source": [ + "import csv\n", + "\n", + "# --- 40-asset universe: 8 GICS sectors x 5 tickers (real market data) ---\n", + "# Load the committed market-data snapshot (real annualized returns and covariance).\n", + "# Values are stored at the precision used to train the shipped QAOA angles\n", + "# (mu: 3 dp, sigma: 4 dp), so the pre-trained parameters in instances/ stay exactly valid.\n", + "with open(\"market_data.csv\", newline=\"\") as _f:\n", + " _rows = list(csv.reader(_f))\n", + "_tickers_csv = _rows[0][2:] # covariance column order\n", + "tickers_40 = [r[0] for r in _rows[1:]] # asset tickers\n", + "mu_40 = np.array(\n", + " [float(r[1]) for r in _rows[1:]]\n", + ") # annualized expected returns\n", + "sigma_40 = np.array(\n", + " [[float(v) for v in r[2:]] for r in _rows[1:]]\n", + ") # covariance (risk model)\n", + "\n", + "sectors_40 = [\n", + " \"Tech\",\n", + " \"Tech\",\n", + " \"Tech\",\n", + " \"Tech\",\n", + " \"Tech\",\n", + " \"Energy\",\n", + " \"Energy\",\n", + " \"Energy\",\n", + " \"Energy\",\n", + " \"Energy\",\n", + " \"Finance\",\n", + " \"Finance\",\n", + " \"Finance\",\n", + " \"Finance\",\n", + " \"Finance\",\n", + " \"Health\",\n", + " \"Health\",\n", + " \"Health\",\n", + " \"Health\",\n", + " \"Health\",\n", + " \"Staples\",\n", + " \"Staples\",\n", + " \"Staples\",\n", + " \"Staples\",\n", + " \"Staples\",\n", + " \"Industrials\",\n", + " \"Industrials\",\n", + " \"Industrials\",\n", + " \"Industrials\",\n", + " \"Industrials\",\n", + " \"Utilities\",\n", + " \"Utilities\",\n", + " \"Utilities\",\n", + " \"Utilities\",\n", + " \"Utilities\",\n", + " \"REIT\",\n", + " \"REIT\",\n", + " \"REIT\",\n", + " \"REIT\",\n", + " \"REIT\",\n", + "]\n", + "n_assets_40 = len(tickers_40)\n", + "K_40 = 6 # choose exactly K assets\n", + "\n", + "sector_names_40 = list(dict.fromkeys(sectors_40))\n", + "sect_idx_40 = np.array([sector_names_40.index(s) for s in sectors_40])\n", + "# True cross-sector diversification matrix (used for scoring)\n", + "D_40 = np.array(\n", + " [\n", + " [\n", + " 0.5 if sectors_40[i] != sectors_40[j] else 0.0\n", + " for j in range(n_assets_40)\n", + " ]\n", + " for i in range(n_assets_40)\n", + " ]\n", + ")\n", + "np.fill_diagonal(D_40, 0.0)\n", + "print(\n", + " f\"{n_assets_40} assets, {len(sector_names_40)} sectors, choose K={K_40}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "70b3a980", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "QAP truncation (k=2): risk edges kept = 78\n", + "Cost-layer interactions: 102 (dense would be 780)\n" + ] + } + ], + "source": [ + "# Sparsify the covariance so the risk circuit fits on hardware. A small diagonal\n", + "# shift (added after truncation) keeps the risk model positive semidefinite; at fixed\n", + "# K it adds the same constant to every portfolio, so it never changes the ranking.\n", + "# Importance-aware QAP truncation (Kotil et al. style): place the qubits on a line\n", + "# and use a Quadratic Assignment Problem to choose the layout that keeps the\n", + "# strongest covariance couplings within routing distance k of the swap network,\n", + "# then drop the rest. Unlike a fixed top-k cap, it keeps couplings that are both\n", + "# large AND cheap to route.\n", + "from scipy.optimize import quadratic_assignment as qap\n", + "from qiskit.transpiler.passes.routing.commuting_2q_gate_routing import (\n", + " SwapStrategy,\n", + ")\n", + "\n", + "k_truncate = (\n", + " 2 # truncation level: larger k keeps more couplings (deeper circuit)\n", + ")\n", + "_dist = np.array(\n", + " SwapStrategy.from_line(list(range(n_assets_40))).distance_matrix\n", + ")\n", + "\n", + "\n", + "def qap_truncate(Q, k):\n", + " w = np.abs(Q.copy())\n", + " np.fill_diagonal(w, 0.0)\n", + " mask = (_dist <= k).astype(float)\n", + " # Seed the QAP solver explicitly (by default it draws from NumPy's global\n", + " # RNG, which SciPy is deprecating) so the truncation is reproducible.\n", + " perm = qap(-w, mask, options={\"rng\": np.random.default_rng(42)}).col_ind\n", + " keep = mask[np.ix_(perm, perm)]\n", + " Qt = Q * keep\n", + " np.fill_diagonal(Qt, np.diag(Q))\n", + " return Qt\n", + "\n", + "\n", + "sigma_sparse = qap_truncate(sigma_40, k_truncate)\n", + "print(\n", + " f\"QAP truncation (k={k_truncate}): risk edges kept = \"\n", + " f\"{(np.count_nonzero(sigma_sparse) - n_assets_40) // 2}\"\n", + ")\n", + "ridge = max(0.0, -np.linalg.eigvalsh(sigma_sparse)[0]) + 1e-6\n", + "sigma_sparse = sigma_sparse + ridge * np.eye(n_assets_40)\n", + "\n", + "\n", + "# Diversity: couple each sector's assets in a ring (sparse stand-in for the\n", + "# same-sector pair count). Scoring still uses the true cross-sector matrix D_40.\n", + "def build_same_sector_hamiltonian(D_same, n):\n", + " prob = OptimizationProblem(\"diversity_sparse\")\n", + " prob.binary_var_list(n)\n", + " prob.minimize(quadratic=D_same)\n", + " op, _ = prob.to_ising()\n", + " return op.simplify()\n", + "\n", + "\n", + "D_ring = np.zeros((n_assets_40, n_assets_40))\n", + "for s in set(sect_idx_40):\n", + " members = np.where(sect_idx_40 == s)[0]\n", + " for k in range(len(members)):\n", + " i, j = members[k], members[(k + 1) % len(members)]\n", + " D_ring[i, j] = D_ring[j, i] = 0.5\n", + "\n", + "H_risk_40 = build_risk_hamiltonian(sigma_sparse, n_assets_40)\n", + "H_return_40 = build_return_hamiltonian(mu_40, n_assets_40)\n", + "H_diversity_40 = build_same_sector_hamiltonian(D_ring, n_assets_40)\n", + "cost_ops_40 = [H_risk_40, H_return_40, H_diversity_40]\n", + "n_zz = sum(\n", + " 1 for p in sum(cost_ops_40).simplify().paulis if str(p).count(\"Z\") == 2\n", + ")\n", + "print(\n", + " f\"Cost-layer interactions: {n_zz} (dense would be {n_assets_40*(n_assets_40-1)//2})\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bda8fad5", + "metadata": {}, + "source": [ + "### Steps 2-3: train the angles, then build and submit the hardware job\n", + "Forty qubits is too large to optimize the angles exactly, so we train one β, γ on a\n", + "matrix-product-state simulator at equal objective weights and reuse them across the sweep\n", + "(the loaded values below). The cost-layer angle is small here: the circuit applies a gentle\n", + "bias rather than a sharp projection." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "4f5a46b9", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Backend: ibm_marrakesh | 24 seeds | two-qubit depth best/median/worst = 220/261/300 (best seed 22)\n", + "Selected circuit -> two-qubit gates: 787, two-qubit depth: 220\n" + ] + } + ], + "source": [ + "import json\n", + "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2\n", + "\n", + "# Pre-trained angles loaded from a file (training is slow; QDC pattern).\n", + "# Set load_params_file = False to retrain in-notebook.\n", + "load_params_file = True\n", + "params_path = \"instances/qaoa_params.json\"\n", + "if load_params_file:\n", + " qaoa_params = json.load(open(params_path))\n", + " p_layers_hw = qaoa_params[\"p_layers\"]\n", + " opt_betas_40, opt_gammas_40 = qaoa_params[\"betas\"], qaoa_params[\"gammas\"]\n", + "else:\n", + " # Same workflow as the small-scale example: qaoa_training_pipeline's MPSAerEvaluator\n", + " # evaluates the QAOA energy on Aer's MPS simulator and supports the XY mixer.\n", + " # As in the small-scale example the trainer maximizes energy, so we negate the\n", + " # (to-be-minimized) objective sum; the 1/n_obj scaling matches the equal-weight\n", + " # point of the sweep, so the trained gamma transfers directly to the weighted circuits.\n", + " p_layers_hw = 1\n", + " training_op_40 = sum(-1.0 / n_obj * H_k for H_k in cost_ops_40).simplify()\n", + "\n", + " dt = 0.75\n", + " grid = np.arange(1, p_layers_hw + 1) - 0.5\n", + " init_params_40 = np.concatenate(\n", + " (1 - grid * dt / p_layers_hw, grid * dt / p_layers_hw)\n", + " )\n", + "\n", + " trainer_40 = ScipyTrainer(\n", + " MPSAerEvaluator({\"matrix_product_state_max_bond_dimension\": 24}),\n", + " minimize_args={\"options\": {\"maxiter\": 80}},\n", + " )\n", + " print(\"Training QAOA angles (MPS simulator)...\")\n", + " result_train_40 = trainer_40.train(\n", + " cost_op=training_op_40,\n", + " mixer=xy_mixer(n_assets_40),\n", + " initial_state=product_init(n_assets_40, K_40),\n", + " params0=init_params_40,\n", + " )\n", + " opt_40 = result_train_40[\"optimized_params\"]\n", + " opt_betas_40, opt_gammas_40 = (\n", + " list(opt_40[:p_layers_hw]),\n", + " list(opt_40[p_layers_hw:]),\n", + " )\n", + " import os\n", + "\n", + " os.makedirs(os.path.dirname(params_path), exist_ok=True)\n", + " json.dump(\n", + " {\n", + " \"p_layers\": p_layers_hw,\n", + " \"betas\": opt_betas_40,\n", + " \"gammas\": opt_gammas_40,\n", + " },\n", + " open(params_path, \"w\"),\n", + " indent=2,\n", + " )\n", + " print(\n", + " f\"Trained angles saved to {params_path} (set load_params_file=True to reuse).\"\n", + " )\n", + "\n", + "c40 = ParameterVector(\"c\", n_obj)\n", + "combined_cost_op_40 = sum(\n", + " c40[k] * H_k for k, H_k in enumerate(cost_ops_40)\n", + ").simplify()\n", + "qc_40 = qaoa_ansatz(\n", + " combined_cost_op_40,\n", + " reps=p_layers_hw,\n", + " initial_state=product_init(n_assets_40, K_40),\n", + " mixer_operator=xy_mixer(n_assets_40),\n", + ")\n", + "qc_40.measure_all()\n", + "b40 = [p for p in qc_40.parameters if p.name.startswith(\"β\")]\n", + "g40 = [p for p in qc_40.parameters if p.name.startswith(\"γ\")]\n", + "pmap = {b40[i]: opt_betas_40[i] for i in range(p_layers_hw)}\n", + "pmap.update({g40[i]: opt_gammas_40[i] for i in range(p_layers_hw)})\n", + "ansatz_qc_40 = qc_40.assign_parameters(pmap)\n", + "\n", + "service = QiskitRuntimeService()\n", + "# We pin a specific Heron device instead of service.least_busy(...). least_busy chooses\n", + "# purely on queue depth, and an empty queue is not always a bargain: on Premium/Flex\n", + "# accounts least_busy can route onto ibm_miami, an *exploratory* Nighthawk QPU whose\n", + "# dense square lattice suffers crosstalk that scrambles deep circuits (its operational\n", + "# coherence collapses under load, and a 4 ms default rep-time inflates QPU cost). That\n", + "# is invisible to the calibration-based preflight, so a run can look fine and still fail.\n", + "# marrakesh is a mature heavy-hex Heron; pinning it keeps this tutorial's hardware run\n", + "# reproducible. Swap in another Heron (e.g. ibm_boston, a stronger r3) or uncomment\n", + "# least_busy below if you know your account only sees generally-available devices.\n", + "# backend = service.least_busy(operational=True, simulator=False, min_num_qubits=n_assets_40)\n", + "backend = service.backend(\"ibm_marrakesh\")\n", + "\n", + "\n", + "# SABRE routing is stochastic: different seeds give different depths. Transpilation\n", + "# is classical (it costs no QPU time), so we transpile many seeds and keep only the\n", + "# shallowest circuit -- a free reduction in two-qubit depth before anything is sent\n", + "# to hardware. Only this single best circuit is ever executed.\n", + "n_seeds = 24\n", + "best = None\n", + "depths = []\n", + "for seed in range(n_seeds):\n", + " pm = generate_preset_pass_manager(\n", + " optimization_level=3, backend=backend, seed_transpiler=seed\n", + " )\n", + " qc = pm.run(ansatz_qc_40)\n", + " d2 = qc.depth(lambda x: len(x.qubits) > 1)\n", + " depths.append(d2)\n", + " if best is None or d2 < best[0]:\n", + " best = (d2, seed, qc)\n", + "isa_qc = best[2]\n", + "sd = sorted(depths)\n", + "print(\n", + " f\"Backend: {backend.name} | {n_seeds} seeds | two-qubit depth \"\n", + " f\"best/median/worst = {sd[0]}/{sd[len(sd)//2]}/{sd[-1]} (best seed {best[1]})\"\n", + ")\n", + "print(\n", + " f\"Selected circuit -> two-qubit gates: {isa_qc.num_nonlocal_gates()}, \"\n", + " f\"two-qubit depth: {isa_qc.depth(lambda x: len(x.qubits) > 1)}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "e7df21bc", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Submitted to ibm_marrakesh: job id d9choesjeosc73fg9ma0 (24 circuits)\n" + ] + } + ], + "source": [ + "# Submit one batched job (job mode; a single batch needs no Session).\n", + "n_samples_40, shots_40 = (\n", + " 24,\n", + " 1500,\n", + ") # extra shots: noise lowers the post-selection yield\n", + "c_vecs_40 = random_uniform_simplex(n_samples_40, n_obj)\n", + "sampler_hw = SamplerV2(mode=backend)\n", + "sampler_hw.options.max_execution_time = (\n", + " 600 # seconds; guard against runaway jobs\n", + ")\n", + "\n", + "# The QAOA angles are already bound; only the objective weights c remain free.\n", + "# Assign each weight vector to get one concrete circuit per point on the simplex.\n", + "bound_circuits_40 = [\n", + " isa_qc.assign_parameters({c40[k]: cv[k] for k in range(n_obj)})\n", + " for cv in c_vecs_40\n", + "]\n", + "job_hw = sampler_hw.run([(qc,) for qc in bound_circuits_40], shots=shots_40)\n", + "print(\n", + " f\"Submitted to {backend.name}: job id {job_hw.job_id()} ({len(bound_circuits_40)} circuits)\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "9933828f", + "metadata": {}, + "source": [ + "### Step 4: Post-process into the Pareto front and read off the optimal portfolios" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "226aff68", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Feasible portfolios collected : 296\n", + "Pareto-front portfolios : 15\n", + "Hypervolume QAOA vs random : 14.145 vs 12.973\n" + ] + } + ], + "source": [ + "result_hw = job_hw.result()\n", + "\n", + "# Post-select feasible portfolios (exactly K assets), score on the TRUE objectives.\n", + "feasible_40 = set()\n", + "for s in range(n_samples_40):\n", + " for bs in result_hw[s].data.meas.get_counts():\n", + " bs = bs.replace(\" \", \"\")\n", + " if bs.count(\"1\") == K_40:\n", + " feasible_40.add(bs)\n", + "\n", + "\n", + "def evaluate_40(bs):\n", + " x = np.array([int(b) for b in bs])\n", + " return np.array([-(x @ sigma_40 @ x), x @ mu_40, x @ D_40 @ x])\n", + "\n", + "\n", + "fis_40 = np.array([evaluate_40(bs) for bs in feasible_40])\n", + "pareto_40 = filter_dominated(fis_40, maximise=True)\n", + "\n", + "# Honesty check: compare against the same number of uniformly-random K-asset portfolios.\n", + "rng = np.random.default_rng(0)\n", + "rand = set()\n", + "while len(rand) < len(feasible_40):\n", + " pick = rng.choice(n_assets_40, K_40, replace=False)\n", + " rand.add(\"\".join(\"1\" if i in pick else \"0\" for i in range(n_assets_40)))\n", + "fis_rand = np.array([evaluate_40(bs) for bs in rand])\n", + "ref = np.minimum(fis_40.min(axis=0), fis_rand.min(axis=0))\n", + "print(f\"Feasible portfolios collected : {len(feasible_40)}\")\n", + "print(f\"Pareto-front portfolios : {len(pareto_40)}\")\n", + "print(\n", + " f\"Hypervolume QAOA vs random : \"\n", + " f\"{hypervolume(fis_40, ref=ref, maximise=True):.3f} vs \"\n", + " f\"{hypervolume(fis_rand, ref=ref, maximise=True):.3f}\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "60f30d0b", + "metadata": { + "tags": [ + "hardware-unrun" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "15 Pareto-optimal portfolios. A representative span:\n", + "\n", + "tickers held risk return cross-sector\n", + "NVDA, CVX, WMT, CAT, HON, NEE 1.065 1.97 14\n", + "NVDA, BLK, WMT, GE, NEE, AEP 1.038 1.97 14\n", + "MS, JNJ, WMT, CAT, HON, AEP 0.798 1.96 14\n", + "JNJ, KO, WMT, CAT, HON, RTX 0.692 1.81 11\n", + "XOM, GS, JNJ, KO, GE, RTX 0.746 1.70 14\n", + "NVDA, COP, BLK, PFE, WMT, RTX 1.007 1.64 15\n", + "JNJ, KO, WMT, HON, GE, SPG 0.733 1.54 13\n", + "NVDA, KO, HON, RTX, SO, AEP 0.628 1.51 13\n", + "AAPL, MS, WMT, HON, AEP, CCI 0.794 1.45 15\n", + "JPM, KO, WMT, HON, AEP, SPG 0.664 1.41 14\n", + "GS, JNJ, PG, CAT, NEE, CCI 0.787 1.37 15\n", + "MSFT, JNJ, KO, CAT, D, AEP 0.512 1.35 14\n", + "AAPL, EOG, UNH, CAT, AEP, AMT 0.777 1.12 15\n", + "XOM, MS, PFE, PG, RTX, DUK 0.579 1.05 15\n", + "MSFT, JNJ, PG, HON, AEP, CCI 0.545 0.57 15\n" + ] + }, + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The answer: the Pareto-optimal portfolios. Every point on the front is optimal in\n", + "# the sense that improving one objective requires giving up another -- the front IS the\n", + "# set of answers, and a decision-maker picks the trade-off they prefer.\n", + "bs_list = list(feasible_40)\n", + "# Boolean mask over fis_40; keep_weakly=True also keeps portfolios whose\n", + "# objective values tie with a front point (what the strict filter would drop).\n", + "mask = is_nondominated(fis_40, maximise=True, keep_weakly=True)\n", + "front_bs = [b for b, m in zip(bs_list, mask) if m]\n", + "front_f = fis_40[mask]\n", + "order = np.argsort(-front_f[:, 1]) # show a span sorted by return\n", + "print(f\"{mask.sum()} Pareto-optimal portfolios. A representative span:\\n\")\n", + "print(\n", + " f\"{'tickers held':40s} {'risk':>7s} {'return':>7s} {'cross-sector':>12s}\"\n", + ")\n", + "for idx in order[:: max(1, len(order) // 12)]:\n", + " held = [tickers_40[i] for i, b in enumerate(front_bs[idx]) if b == \"1\"]\n", + " print(\n", + " f\"{', '.join(held):40s} {-front_f[idx,0]:7.3f} {front_f[idx,1]:7.2f} {int(front_f[idx,2]):12d}\"\n", + " )\n", + "\n", + "fig = plt.figure(figsize=(8, 6))\n", + "ax = fig.add_subplot(111, projection=\"3d\")\n", + "ax.scatter(\n", + " fis_40[:, 0],\n", + " fis_40[:, 1],\n", + " fis_40[:, 2],\n", + " c=\"lightgray\",\n", + " s=8,\n", + " label=\"Sampled portfolios\",\n", + ")\n", + "ax.scatter(\n", + " pareto_40[:, 0],\n", + " pareto_40[:, 1],\n", + " pareto_40[:, 2],\n", + " c=\"tomato\",\n", + " marker=\"D\",\n", + " s=40,\n", + " label=\"Pareto front\",\n", + ")\n", + "ax.set_xlabel(\"Negative risk\")\n", + "ax.set_ylabel(\"Return\")\n", + "ax.set_zlabel(\"Diversification\")\n", + "ax.set_title(\"40-asset Pareto front (quantum hardware)\")\n", + "ax.legend()\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "9bc07639", + "metadata": {}, + "source": [ + "## Next steps\n", + "If you found this tutorial interesting, you might explore:\n", + "- Replace the embedded market data with your own returns and covariance estimates from real price history.\n", + "- Increase the number of QAOA layers, or train at 12–16 assets and transfer those angles, to push the hardware front closer to optimal.\n", + "- Read Kotil et al., [*Quantum Approximate Multi-Objective Optimization*](https://arxiv.org/abs/2503.22797) (Nature Computational Science, 2025), the max-cut study this tutorial adapts to portfolios.\n", + "- See the companion **unconstrained** notebook, which follows Kotil et al. with standard QAOA and the MPS training pipeline." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/60f30d0b-1.avif b/public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/60f30d0b-1.avif new file mode 100644 index 0000000000000000000000000000000000000000..d18615940f74d8e26bdd938d0e67fcfc3b4dce5f GIT binary patch literal 15939 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 literal 0 HcmV?d00001 diff --git a/public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/9ccde65a-0.avif b/public/docs/images/tutorials/quantum-approximate-multi-objective-optimization/extracted-outputs/9ccde65a-0.avif new file mode 100644 index 0000000000000000000000000000000000000000..0d5ba38ee4b04cd532fea5c92717d5ba0d574174 GIT binary patch literal 20136 zcmYJZ1B__F(lt7^ZQHhO+qSJUwmoykwr$(CZJYnO_r5Q$lHIk|s;cZ=o$RDL-2eap zIA$)M4u)=)W&r=p*3yjeKiJmN@E-?sur+lt{Ez<62rW!(oc;#^030lhUH%XMpNDg> zbg}&}0sJ?zSlZYd|A&bP0005}M*#p#{+|Z`keU0Z{WCd}|5E@&{!tD~OS}Jm#($%z z|A^^-)BaT(x-l^Rr{ez?|3&^!thID-a`+dFv~)1G|L0*0-57-fKw$n;fMV(FVEP{b z001QZ?~(fl5F9K$EdQebLqI_MbN^pH&VQtTUHmTw_@4-ep{s}Re`HrDJ1$!XQ}h20 zLR`j{#tvN0p3W|&w$5Dtax85u4V^r>3~d}N{ySf|mJYW61^-I_bq^c>1PlNK5)1?y z`cJ^JbTa*4oBv+-uh{0l6ZEf1SsYsvLl-0fC@4%a6HE3$AS_r2rk=lmNB}^Q6!`*A zy|Ad`UZB;=okw!R+2_dITZAxtF3w5%W;^rcx4)m+Ff~5*#WK)+NTM@UZmXPCoIz)F z)}_de{Bb=32fcexD#E)xc-0cDj4t44-=TqaGaH9Ck zH?Zip_YImdtVY_ZH^dA&UH4pP#dDbDhK&gmliXoL0w|cD@ujeXNm)a-(7!z75xbw5 z$y;6mDN-!T!YvCvxk+R8m3@bTTiH6re~NcI(9e+^qHKd?^$+ffWwnCJHj1VQ<<1LS zt6mvZEw6Wn@jeCa@l#Q0Zg;(4L(7r1akMi78>$K5YzbD{afh%D4Rgdv?__!(D0sgH zqW9C~(>-+n!3@$0pJulaKDljGa%e{jB8TPTR#tkC zUxJ&H6vNlO#ol_6x)-W%Z#H5Wad0Yd0vnShi(Y_0ZxFvb`hC|R2&ahYv9>9nltI)M z8&_GPx9Wh*cH9V?hpE;)1*P-M?Cc27QEsMOY746=FDiNhm)Q0_xT;OPajh)WN{>(V z;6F(3U9A~DCnc+QEPwAhGswSD<-9KMa>4dTJ@wTh#-s~fH-V87ZvH$ve3 zy}Dw&%d=JY!{a1IwpIh`U?z?@yeaD+S=`J$HV;n(?Rto{^KwRq0|pyKSnlBH*NbI} ziI1+>Qq37fz;-K0^$3y7peEVrEA-P{;t{TDP`YE|G}LTPe9B*mM$Q2wy#m3+cA+rQq!9BE!KR!*jz{{e^&GVm;_T{gF|Fgn7p5JHIH>xZQ>Id3}Y><`A$7 zp8Kjsokh4nB8WM5tpYo7tdqeI|0#@c%SP*~{`Gzh!0NQq*IbUDDd7z|p9!vY+7zb) zXc<`&+m{5dH^}jV)CERi&R;qYDnIE5+ znNq-RMDOasj05~@r(#Z%VhFd4P((;@z@(VI_vPao-gZy@9Z;KR-Rupm83MY5ICx%X z>FtuJZr8R796NdvN{G3kEFe_>8$pj|J2VcC>;x4|mJ!+E@9C4d*nrPLqC(*HFHeYF zk{Tos`2sw;SksUE5bf?2dp8(e{T&!MK>tR>HLRSD$VF)SGhUaXDugJo9 z@Vp$Xw2J!>lX014Ei~Iv{)ZXXg!Xj+@uj_^v4|s_b&**xXI!!qY7tnHSOR?I;mmt1 z5U}0hT47jF=t^qf*)h7X$vo?}Pd3NEHl(z;7qrq^#jVTw=y)8ARf4f1JK30Z-YNfF z^~)h$5#|OiD$Ea5t>4WMJPZZPp=;NaC5KG&-`P7Qw^bDK?fr;mn*wpCMSI_^ivw5Rj3 zmR{GjVW$QVC>PHOG9ca53xeLFzmT*p9v#%nGPJf&t;FJZnVhLG^0!^juc69|*F`T}pLJ%l zL+0Cf83XO1s+9K|VCW+wKjtbhT{g{Z500v)P2Db9oTZY;Gz{0-PMRq?Ekd7l%0LBb zCOuvB+Ia0qlt;HN%F|vAb24JB_sc48iL1u@b^Y?547N;m|6oxs0s#RiW{e3js$dZB zctX_egaB6E_-;gBoDD?7)Rh9|h)Q9kr1{+NTQca58WYY59-JpU@sN9OIVvq2Lluq? zoEHRy5c@J^UMDxp(>!Hx>!ohf^_LFADVk+gYyvwm;Gl(TH!5Pk^$0E*KMX~m^6gjz zGfOBq+Yq(09;l2axQrL-<`B2Tj%TKc=QuJ`*@DfedY>H#(CT9XL64yvGAMBL3MvJo zNZm8S_&$;b+CUo*?7kSfLhUEtz$9$=xF^gW8-yN6@$``pb=Z#!0Ivl#}q(Ormak!OpqJ7FAc2Y~a#mx^Z zu{TmwES%S4HgQC_JZ@^k(7N3RCYKifi5mQa90E6X%hQ>fEil!7Ul7y62WlO461K22 zBnIl@4Q=9R?Oix=7_vo&&^FJUWM_p5?gF*kk^eKpfY=V)CDfxmqT!*TZ^bP7)?&{tv9UP!ZXpY%18E|SD3RP5$0<4GAJ|_vK z#Vs;bd)oY_cDbyI_i|ECm@OOTHKG}E*tfMPe10S^iZlGBs`7!4*W<&I2Ttyw+v6^- zocWF1RMq~1DIA--4czG@6WR=-nN#A<%I_(`YPBAmq`q5hJSmR#p*C91i7) zTuPwFmEjPh->=_r&pzn+#Ngil*AXmpG3{t)jI8%`-4Cvz*|p0{%=QEAFum4qPjqWtC$AA(S9*8K05_Gog$;f#!raNNbNJ zd^dCUQUhVgt4JGWHzBW~bWlhRvFvxKd!gOsib!Pd!SMn@NV=ro4e0dHR?$(Jp;J$! z3A`FUv6CUBxfhK*NaJ7UF$?O{_DHR5YVr`lq{HvbgufAMS!x=pwM7pRZ+F>77`j1$ z+5WzIe|ol+{N*aj%@31AD(orY+Wt}MimYA|Fy-_zFT^x2T4)pUb{c;T&yv+7u9+| zVKpwR51BX032c6DZzb-aPuK2~}XBY+QC6heTvhStyd%Y<^J*!>&>c7H} zCD0-AJ-c*wLX{>PC`j;o<;(=?#GKQdUeS0bsK$X;L*@rxF6`3^vJS0fZR%{k+2YxeQnxl@;uI-S$t&a}ZB%_!JDTRU2oGv1g7#ccAN z0fo+t(Nd@{#c~;w{BGlaAWnv1&ZZq=>yfH^6lOaq+g68wYD=>N?v0GudHwaW4g6$u=|1SC?u7vY=7quP z)v^rXBnoAAmE#?G@qtl1LkS2pnG^hy6O^{R@98^RI(AGH(_oWrxilpKs!LY`3y}zV z#`CETA88h6zy*?w$J~_y>#<9ioH5zGes^H74GhiT>f=+#!>ZXYcenyaf!sMWU1`W5 zcE;w_ozfk*j@~Bm)u|0_%3LY&lp^+Xw6sfIHH>aDJr7Avr#`61O{|3(y~tOM?x(QK zOKQ>x)`9N^#~6K zwM6tYv}u_~BBLZVd4mx>G2!7cfaA=qQ+!#hx0t7@0xB_VR4|O6tQn3qUHv6mOpzSV z3NT|lTb0p<=Dw~u@~pgWxv~i>8xx$KSVKVyir19^YO`mc>w}It;&-Fa8|8Pfnj-J{;~oKGuYNpgxxHG{?N2Gt;$asY$uCD3FMA8Ok+n%AXxOl;>9=Gf zq7VxOb|s_Z>GIoX94(CzTLpxpWPj%U$_iV0<0n9doF^~+6(;{zv*R4ZEokL^%o#90x3p8od+d%iq?$~&B^ ztm$HprenJ4`z<)H42@RJpDwY2YQUo6Q{dkcx>d?+3k^m2hsR%aBBkhSrxSIl|G+(g zwZ{lG_Qi#CG7QoIlX+rOeZSwR8mjOfrxI7E9AxeadlyhsVfDhh?z@)?R@3*)Pc2~O zPBn^2-U%{)47au{nXf(A%>v0o7?BQ?nQqcU7S@Xi3p|Rbs>triy<9vaaqncEIw~M; z$V0d%1*;#Ml4msLn%c!&S}UN3i5Dfs^?}+^n1(>{sOm-8eqHsCa$;K}@>QM?L7YSp z&%h#y8eWp|lQ&d?aRnskj!N1W*Z8q0*0LqqLY3Urnm7$2DHTeJ#s%)rzic50_QAIJ~`9F>DaJ5$7cf*d*c1S zR45JyeLxm~cg)G(Xm@bq-K&-vD3q86{!XG15zgWK+po(c`>y$*#F#4BlAY>wu;4hA zb8g$1;;`UKnnCe{t*a7wy0f$jVUb?Z-@4G)>RftB6wqcQPrN=FR}=el_lv~I0cwyv{H&02zk4Gfg*C-cB9iK^?>~_iJ9H4GJ-%@ex-qtE5n+q0<0&1dDDjX zEl0zeId2`>w5L#uSIbQ2;ia_Ri0DGrs2ze3#Omw2)ieC?2E@PpbU{c2!7JzE5S9Yn28bq#KMc-Tfp04(uMtT0O6lNgk^9~XC*&M6t9txw*FRA!iSZtFz=}fDf1x4_``%@E&w|bfi-26&72aqAP`Ia!-XiN7EB8Hp6KmSsN~x#d z7pgbXQp|1aW}e6Kv*dhs`JsO8Xp*v_g!9el$`O8C9c6R2B}xrc^Q`N-XP~3ZX|OV$ zpf$pAj~b=IGCe@~CkRX3Hd0DSHP=XYP5vTv!BkYJR8$SBF&{nB|2QIsA4qD^e0h)@ z_8DXK9eG9T`jrB-ESD44G7Z`fw7rZYO)ZTTW>0=cE28o{NQq!gAQoEWP6;=U1ZhgpsN- z9%}&IY8ub4cSP<;#+i&>jS+P z$#%QewX6z&OH$2=_`#}O#VNkqgsVBH`xR)Gr_(^YGl@d5b+ysaJ;r1)*@W}qHK~$2 z@w-Q`L%9=Ja-g{Pq8y*Eb@xnXrypd5Mo$wBb$vc26Zjg`y$Tj1A_nl z7WMCq9uL#si;G7{#H5G-k}=LRIRSJA@P^YJLB|GJ7sZ+MMyN5$lj$JA3US5as!Zz+ z!U&P{duve)SY=!e%7}=4?`0zsTR|VvP?JxhvQvEmbKZc>Mx|7tap&&2JD*Q0q6$By zOC1z#GZAuI&+lw53B^rA0R&F~@}6BaXE07qyMm~Nciv7wgmw6cbX%ometWzNb5bv9 z$uj@Bp>iP9X;6iezYi8`%u$okWDPhZopGObtOZE28{?-8uzg7%&RhDVy0)s8x!BOy zj7I`#w|12y&qEgXIq0dE<-swHc2Dr@9?;bSp66g*5ECFL!&uySHh`Un5WeMSXq|Lt zd(Ek@M=?ja&uVMwoeemn9Cei!nA!aYtLlO5sYo$CZq)5p3pcO39fGoo)M_BM$>{z0 zg|3A!p3Da;fdI)zj~jpz zsKc@`DRxZ(YxFBFx@yGbbM?s~1d~EH^|e&CDkJVaHKoL&lvcPSiH_;RGWMa4JeOwX z(*TXrp0yPDYUh~!(~TrefG`q_RT%J%&%ech!t`SS40Z-<+0~`uOS_B2gA)|Er32AB zc~$OsSZXW<@~8anYzE>;ZP>Efu8+9TK&2_pM176NXy_|I&KaZg(K`y_=F#LeqJPXW zg6E_RqIN*jpPayHCkXa;onnGOi9uPWh6eu7JlIUSan}k>()8$_LuT~xgc@+u;VcoE zSxb2;G@q5}P|Dk4(eg;&_@TsbQWfZhkBGhB}dl;+=aaPuA4`BC*jqhgX8Q=M7f zt7sCUgQl>y6MwH5N|7pHC^@iE=~-5Li1-Zx4{Y#RgqwJdyyX95%<9Pal!tOrxlCR!V zpt6lJbeBC16_)z<;q|Nh7alp|jyoa+H?_Yf3nMg#aguOtQ5b9NRrRkjH}AdJy`*WB zPt{VV59#iq=E85p=32>%NZOEcWpBov+`CzxIPz@3XT|Sc56f9}qWW2Pvgon*pfsm=Vlm>QkAjQPqa`QFUe#3>*kc zuv_IVI~+eyGeD(2BtSUJ7R&OkD?U=+8&=xPuB=N*!|qzgmGSEpN% z0l)G*!H5>K0xLB-Uo^N&jimxYGe18$cOpfer7V*KN19i^dXrwXbqQvCzm+Q`B}ri1 zy{kOb8pxbJr+h4X{ zz?c$?PNulb;RxEPD>xU79cH39E1jpZ4AzY{>x2M&Jde!cSP;*HfV##^%?=MrzsyS$ z(&pI>ngAuh;S)rgo;*w!;zH@q;;?m&nfn+0rR2%_dzve>755NS8>E3p8P>kX(+eI2 z>Mih)&e?RZntYWP1DFQc?Y|~Z+%ujqbjy0c?Mu|Iz6)?C5#vWZjbX^AsgOR#FWwtu z7W;GTmI>k1n!58(l~T0eG5(NMYLN`9%(K`GGit~<2{==95`{e&mcM!mYVe{VET+WC z+ueJ=G<@Viz?jE5SIV6Li`QR)ua-F&x?YH>vB8;v$0-U3rgc!Y2gUpJp~`op0jJ&+X8t#r)c&c-5_4;He^gITry z;ssTc3-nj7jaq!$4T=)e1koNM)k_Vg(e^&Q^{kmA7nnGQH?nO|O{da&%*DyA2qDLm z+HD5L5Nk8>6zn$^rl<;4V6O{?vs&WwCqOB>H1CQU7SfIW6CR0>u4GzgkOcRV3Va_o zy}icblK%h;zC{#3f{MEP64|#$=|H&q1K+fxPC>@oQ3)C^cUEZ3V0A8(4)`Wm1fgS{ z`aCny0%wFIOdS6eBbHvN#2vn@q$5#!ZMN&Sx^1oa1;!oEXDK-YAQ%v0+`_HYt?Ler zB_OS_Ueio4>?k<_Z}BNoWglnqgQt3LrjG)IA2K&8;5m=$v7x8arxM2a;Q`=^z5xzs z!Iwj=d~YOtqizIk`IfQRdG2iyB#AC4873onG})vyIYdzDY^HFOL152>GM&1@_b@jd(m zqNRM|)bX9ixQumN=%gJhA^h>vOicUS;Ed93NS4^*Z$12v;%Tu42q>NNzxcw>UJ`4NFFe@!-#kV(zvW zf%o2YFDFeIOxdfU-H_${U!v^yhJc^ds8z~BQt*~F+&*D07wW|$|5?$)K7XB^94#|I zplFQp!9eG+VzQDqxh}MW-1Fu9UD!>AR|Bw1=cbjRqp{qsBu=2mf*o)mJ?c#ut4>xU z7unuCCBq>hiqrCN8$cR`qJlr`|^=$B;lXYuhhO?_IU7>B8@&MYtKNTO{zVyNhrGH@C}Q~X@MwRT`I zH4t1Qwkw@S_bXMEPr5s;Q9iS z51BdzWd~VLd%+whSLym1wkmfx77syE3a@zE1B_~_p?Ep~c(P>SPQoRvcAnS01gMrP zP=iu%8EXJ)LumVSE~t&6Ay^B#?Y)fupyxbkv<*cjC4voq1%$ddAnrM3m~ZuNtMpzZ zLTfU49Y&cdmnAXE64l!Y_(W2+c5?`C8Q%x6u@o1{srjz?FAbZ~LXuKUeO{$vL2;q_ ze~~f268|# zt0+CIk4`dkr0?t)Dq_7Fm>CA}@6No1X#~3?k~A4gqlJ@%QckM`=aOkld>GHKFbx|M z1W9Ocl4&lzXe?zebFO4)MqG(w2R_XQO_KHN@)BQ}@>Yc)cS0D5PzS`u4}6~?WXRZK zp{vi1q+Y~pRzjB*2I(kBzsuvrnkKkbTAa#)oa#`znXF`C#9dfBIkaSg zQAS)|nA)qN(Oo!Q2Rgl;pPkptti}3fHC%mhILtf8@gSv`A0X?d$t?Z!16&!SJ3UNM zA2GSEEs#bl=y5EK@S1q;dl>#Lj$f#5!=0|o+f$&B6m@qd*Wh72PEJTd^fb{LpGN#v z1#{fGjB=;Jk>P@uk;18Wnr#h<-xxpz%ASJzlr?g3%G!7jXJs_u)8MwrX}*JFda)^n*MtOv|u_lcv6` z0ogQVf}-H_r6unw#$VLRaJL713IiUiwJ=d$E?8_&Si7NwvB_bKHRSG&P+o)BKfFlX zXyx;*J_~LnO^gFr-Rp2VW3V|4$ZSAe1h8mVtKv-t)9z4%K&1($yN+LO1@Hcp3YW*n zLNEW&JyKxaHb=Y4OnUnn^lM{X;KarhyxDzL##Rh+G00Gfr>+0xCGP!% zx~JDm;_3AxLbeG5>ycvMD%vp;go10g*RXX=v!x{H*yw*0E&Q~tATveo0W@%0rBHRU z_f_!<`b!&>tHVbYpI=fgJo0aI>`^Kk>l3WJ3j>j;GfkEZ8>Jhh=o<$l%PJtP5%@LqO`_eSh*f(8I?T4zE@=U%IGtm+HHr2KSi&Vt*1pctHaIf62QYMgIVc1 zm#w%PeGa2vt5&ld=V7L?B^Mxa^u=eiNdvhXLo4rFVc)A)y?@HH_D~;^V2ax1hvgr~ zP@K|25;%}Gp?wh87*psqnL8BETVu5aSk^_@GUWN2Lr|&wwum0Ka=p1K*`ZTqaQ;lD zBH_K;4!pVNb|5|(Y68vClWEDWWdAq?5^c{RzkZQuvk0xKX9jXqlmxHLQFn#B+`$#LP{P zOkSo077z5bMh?r;XIO1uFhm!zzFTX4DTD1Xz=%TW3cT<@E6Z6~Rdk@NgZYA&dT2V^ zBK#`iu9~W?13YB+~5&QjK-JvxF5lgD0aNnJvszAbR7n16P z;_*OAB}{xqDg=N`aRY4+1Mvn)BZE>eM;v}_5|A#XC+Ch+fr2=r5hhWh<^P=_Z%mVn|<)fbna6Ox{6AG z5ES?&UojJGy%Q{mZ|q>!59DcrNIwCO`VGcxp#WO1t$a-s)HR3*}Dic1x0tET;FRCDYQN%^i#Jhx!cUdBk;;~6Oyu0x`g zWGj+8Q39n>f4KO%=n2f?kPk(sFcAJMrn;B2m^QCn>WLCV=G5@sF&f(66^i<5ic$!v zwff--*@^&+M@ZKai&Ubu3C#{BFXFuV38ie`i_$bEVE#+?+V?6vFYHPOk!1w{S zE!_+-Sw4-_EK{H;*tH6Q@NCH7vov|_8aCo}xfUrTmlS3q8-bcO>*QJkRB`~TvA_b* zS=ObU#j3(o&_g+BO+^UO34h04x+$ql3k(mo^#_x9*zGP9i!7Qm9$1LiBaOUkpO7QO zAVcw4%L(Ovi1#?0KL*@b#SDk)vl*Wsc8y6Cm`>I>Ti!IE;|0PeRno!Cc;%GJnYni_ zjcGkfrpZN|)W|Abyt0gm1T>QX3DIbd$o%HG_|Ap8`r?*J`@RHF%<`(e5Q{%6nZb;3 zbwG$DOV0{(TOuC@7JQJ<#h=P|Dx`^Kq)>S{74lZAcG0wTU~pyY-rhU1x1!teOs0p zW8nI3*b09s_InELxZNrlo8Pl=`b+5oUzBodeZvR=u%w>Tx^~tz4=e6+yY|C(5zYk3J-z9>mFRd|2ub4IN;UdSihyV*FUt{F46`Ltb@o-?oc z#6iDh)?Cy{pJx*1tI(K}=N$-mOUoA+*n`ad;gaDLHOE`521TYa>*)0%Z+Wiph(v@6 zfWK(4i5G&qsK?{nmc5gEN_3SNmPO=->UA*jQE<)i5kI==)&+9PvnC#=h2+c618ZwB z;3qRroVY+SiABQL-() zuA1?aCY9F$AJN_`#Np)5qg4-WAvHJYP)|NVX(dG8 zy=ZCCgcfK!7qj3bxYo?NV6m*EH0W!(qcc-js9ngT5EeG9yz|W{&M$K&dOT|WK)_4K zS&QdNpq%wX5upnf`Mlf#F_9Gj?l35X%`&?ac}0rpp}BH#ehiXt*+foO zXzStYu0Uj5Gtx5=W309Pr?SIk8X#tY!dcATSCv@AIUtX8SmONHb3%R@4bH(ucrYN{ zqz*0TS9dS2v;?ji7%fP){#Ijui0U#ln+;{wTmz11*NDyB8xg@-upx8L>_3S zs%>9|b!)(vuRXuAiiLuLGI}WD^9aDZ7gwM{Lxrg&ghbpYB@eU`?gug3iZ=F7+C+PQ z%ML@}Z|ZN7Eb_4JT_el&qcXuMt^r{!3`0LK&mLhzp%QuZxTaoWwFG4uicicHy^jXgcPJ@W3Gbw^%hU1TJnCikC->S?wu97ZldVA}IC}9mu4tX;TP=ss zp4cxsAVldF*>Q?!tyF-aLrS35?pW*10}jm>f5mUxL1tvxB$;mB~7l^2lG#g4TqAliB@J_3BcK1;Wis+4B23s2%`g=kFR(iJL2X1!TBleV8cCk-r$ z1{Q`v{T2iJ{F|2{?!xtMXiEYiK8vN)g?&ux&p}Z7^QM~c)~N0qpVu#YngjaumiY9M zK)^ek7$G@ZyvATB-N6veV`FGjJoX_gV~`8dqT3k~$_V^$QCih%+S&2>t1(5zB0@e2 zyI&i>@NgKS+!aKALrcO)BBxp;8Q2pmDlv9yBfMUJYe?*cuq*20yp-HUrHQx%IJcS6 z6XWu3C;CzqZE6$|=<`Y=DUWa%=cNxZY8As*^SJPF8pi5s5Q23c zdf4n#^O1undNM2D7{70eWKh)9KDPJ^=bJlCo9}y=g|~?hBXV>N82I=k>;0Q0ach4h znDi5tF(1pW@~y*e4un|!S~K>$u8%k31zKd#ApM9KK!z@gea8bHT7R1UShzsiLotn{ z7@Oq91xc8Lh;xdUnSbQrz2QIxm&TtdC0CUf_r4g3{em64SiC#w($M91{sQmu^n@0s zUM}zU3u>6R!J^`mFK?e6Us^uI;n#s8DVH{J$Fz%*;6ksvIX0OMqz^`J4vN2Uo!AEZ zpv7GWPnGmCI3M(Fgo;( z*mRv6a<{#Q8##RqD z@48SZp*vY*TQ@H~qL*vz6``-O4e>F2BdfSVF0iOn8Dy*`1uZ+l5oe6lK2cP2muYQW zo$|%l10B=a&*d_@5fItke}>Aqc)~9~7qZyEgwE$;a&P1-b$s`a5g0qpk)Gi>%*2HT zo;P!0#<2z9^b4#^F17@}RRSuiWsB)2w2=h{aXgO(PL5zSKVW8a0fFHTPv%J$4-H@I z99|rtXz>H1Hu$iXq@oG{R){r;oB{6)EE;esj4;|Dx<#%GA2E}ypReI`gp8aYw34;c zQx$RnElc%VIvWM%u}@pWm$=ZMA!#~AU96q>ki)m)RT|NQdoiWsBz)c%V3IFtIaS7^ z;H?-r5fewIl?14j(o|{Q1nLBa5%kV+)>?HtbyI{24`iN&)+Akob+815NHSfgvD>%{M<-%Dsud z(YSR&;*2gC7x?l23jh-#*S+iQeL_kOM^qGdQK z6#F7?-M#nixfmGharnwf8b>jQQ&&Z_s2jk=J9d1aHXJ`}PME96IoK_Zh&XOPJS9^r z!B7@=Hg`39B9~0-Pmak;3k<*5BMJ!P04C`+iFWwNAQrzToE5LfULx7D`Tc&xrGw>y zs^lKY#FN#TWdZ5>1Q4}7BMCJ~tmgL2lbPqO6-ERgUlekLcNa$ZAe_)G{LKP3dT1TGJzp{@zg z1^kwV!+yxZdyoKlb#ewJjt0FzK(YmAVF_^M%?QvRtM((ZsV{?)Sjwz%d22dO2?OoG^dRx`nv5>GWBUs$IGz~^#gN73p;3Nd!>OT(+si2)q{xKF?PX;P072ODZ8clsFdZG;sJbeF5Sic1ncq?J_ zA`3o{WwJ6-Ac&x(r-%f5l;##puZ=)LzW zQV5!xvkxS6*A?yMP|j~va8Td01FovWnJJ6)j3g54RQ-&Rx%UY8N}D^RxTfSWlA93N z>{W|#=`Ou)=TW1DO%-)@wUfrohI&#_R^O<)E((Xq#Hk}KvbaED1Pp3bEeoA`AdK&u zY!==pv;O@oNf(Zeny=p@EvT@iBzw|0byX19>p^_3pSOeN)RZ#bnjdqmz08z`aVF-F3>U)%0+OJdqK8a{qCPKy=EuFhT2Aj=XVcgRdgUn} z>?3gHO57iDwl1lO?HyU#G0~SG^L6M!3-_1EEF;Kl5rT(d2u1Zdhr&}~JmRO7R8Ax1 z1uGKd&?7=%H3JxzK0F?%0-&HW9Mg`9!M3xX7ugQc95Y&}f!jc*a z1iae=bb-l<-jI}F*`0nI?)7vY8Rr235K4dWdxoP+T}%C|{IhKBpzuK{c*KEBos*}> zzpBU-FOk0{UcsvgghBbS&|ScW>)A_Kau}XR?u5`Aj@aHy(c5Jv`X*D9!(1xJFFXgR z$>Klj-gceRy$G-d=gl(YCGu~GQdRo#Zx{2CyoS-`)BL;+4H_+iz8XfvppgUkNered zFz=Z5*NQbzfX~z>tqqvV38}a-m>vpWo(*Y4`)khp>`aC%?ZO*lqLWWM%^lvU-@Gzb z--Yey28hjTumq;rWL6(I2}&}fEXx6CwVCxwvYMaXS6mf>Svz@(ecuuBep(Gyp_v2z|?%muIy|B7q&*Ws0D9G=`Vubl}gCW z=f(rOA44*};uo(`^J88F>TD@MAxKS8C$kf~mS5YT?}pc){*pu}zZ6r{fcQr{6OFEB zXw&{><{2z8DwXAxWRn|F8DsvH323}{szxWZ3wQ(N!BbIf4Vwi(XEnUmr93oE$#w2P zWYNys<;%}LhwL^ayu^Q|Efv$E0g&p4NU=gB$}ex1^-~vL`LM=CnHP_ng?C_|TBhXG zz2n%TG8i-ckoJ#V2GQ1#jCRYX;~0$k;wj(ngASb1TV*&>*K9P;hExZDm>_Q@Y25-* z`@H@&hsI{JRa&;Gm)q`C%r?04HQO%p#YgKrO)Ygyj~b!Quq64)?&NBB!!TacAz zHvEl0`r-ORaa1F^Vf4=DKvfk zH#;cM0ph{-Ne~jMVA3@2vl%H~KiEqpotdt~Pmq`teig-o{s)Jrhl!b(;z3(ygT`)RL4t%P2voqAvpDJ&fSh|wb2m$ zwt{hM(CuL_kO*9mw$p~=3C>a|D1H`1L!gF2(1p!M4}u?Fkh?-I0EY0u)}67QcjIy{7D`#OJ^qno z=k9qdvmc|pC9QJ4EzT@wHTgA{IHTE;k`^(7dl@F>nk)9o{C6p2ES<9nYFyF_BdO8P z4U)id5fAQKH+ijdZ94d^2srfe)D`ZkjhyZ~QL(C|KVvmOKTX;EK6)^y=eT+KBCM#f{srdTP7(wBMb@Brktd^>u|3|MgEd~f?a-}u055bon9{<;>BS`^Ti8& zulS)(D3&cF#`U%!`(+di%BKbNHsTs48Q29@54I|YYG$i5e+V#&-@Zso$Z$DHH5~5Z zw9^#2pt*~E1F(x;RN;sJ!W@Bw(QnJK3ln{*5R+H?lf@0qFWr6VLaxZOA=!2js}655x4n9RirrVeLS@RTd@D&5b~MNS@H4p&wN#58wY4-vM-@wg2j7^qm{}%P$hZ=RLPLMJW`iaZ) zw^F2iF!Ur*MJq_A>nnn7HJJO{!s38(DxffkW@>2XvR^M~<{0Yg(s)`;6fS|J;{@8& zEh1nwSUyYjO~`q5TDZ z{Ck#xQp&fvvOby%Od2f_`jF+qlU45ZE(e%F<*20?DOVX-44V+P$TN|lWP=dqTaE1E z!DM_6m}Znt#MZfo9Yn;kfJaN>zmX!ePjhUss;oizD{#B}1(=zagg2wzBQU2GAeYQP zWo_$&3mwvLrwLONA_|IL0@*Bq4Yp9X+(j1l_8~~jnZ~F9vg#6{=a^*6aIZ@yPPF~0 z>N_fDy#1007$}>L(KKTRmk(}P;;Y0GKKPrnG`)n>T44ivu=o&RK6_R<3ASa`>0q%^ z%za~Ky5?t}Al9Jz3$TE?yb)90d1PnY2@UL)=)xCjKu_VR;lQ>$66=s1WfpdYFIht* z>n*0NkF2~^GJe>oPSGEi12+?=aB$o_0dHs%ygtY?4QJG0+Lf=s#nj>5qBv$c9FPR5 zOU_yYJ%C4q5QUDXeU2&`+gJz?zYc@&DFT&l10BDZm)H^#4lS{SbI+O2x{#xOISdzY zJp8R`Rwhp_SxK77im~Ik`){fIm!c2CJuL~iOgvQMYTqRXfybc0KrixlgU%31lEZ9? z^(m&8V<(%y$iDqh@_xWw={Jk9^|aidjmApPTNUms5p{^nrVIB7!*{$4*ISjoUzk=+ zo31T^MMFsI^T-<(ln|P+UU+(Mkt}*)ux2=q3blC)Wtp`ZKa{@1nZue%sXOBkii-SLjtz79PW#G|^BN+L`0xkFf^qtdG32=Gi zLA8kU-%WZ^P-Q>;t0oj-KAUS|@9@&DUix^?joQv?Cq8`NULG^g<4~k=pJzsqi|<_L z)Js$f1@L=_;KUT|-~A89ieV=QnSxR&Di+k^W_B_M5Uf4T{}<^E7V?9)xs1*S!hGIS zCqu(|Po-X+0idZ+BJX#3<)H8@bvQ26TwwcbLL=AXNaEzt?kbwqG^pl#si~ z!+;uvrj(kqv>S)4V*@{xFQssq>dS&NH1&2-9Z*A1YFzt?H&fcr*N^@ihan|80Xlipkv8`zt(FR<_IU?b|a zjDge?`dNir9d9diX!cUy_O;As@ra2qB^XwX_8^dO)BF2O#+a}79!Xh_DE^2pP(t7- z82q1p9vk-h4%S@jMEmyV%n6GS#eEY?C+-sTK(gM54a8S{gvqyW75lezHC#pm#TNt7 z+pXzrHjjMK^9{VKSD0kkoP9Evn7YymKdH9bDgx~d)D^&eN#n6D#Tjh2tzRsPkw7U=$fR}P*4G&?CdA&Q=G_KrRE(0t#@81%zR=-KI9F?x9p zAA+>$*<>uV-F3G;Wpv>8ALV1Z;FroH7i?ITo+qn-!MP9+I9~@ovV~Pse;W_qR$Gi7 zu;55C$yiJEk+%rZ7Eo6ZEkuM_zXRP}4QhUJz<{7Rws0gIejMXsT@HFy%z|KMv z_rVB+2<}URD;*ftZoakTQl(%v#(jMvxDskt3 z3XJU)gPw5|?^qb;thG4(`8#@FbzAJ}|=bT^gc)h4y7kHV|TmEukF6hlf} zUl1Flg9!Xjw&H1(NeW@ZayAdbtBVjIjZv??^P7YRadO!Mu^rrwvf6;^d=#p!jUbdb zcDggCN9i$g@u$fowTW4bg9pJ(BXMff+0a#YDFT72FSB?Ow+W-e3&Z*I5lmym6Y!oj z+^Q#)^AStw0ur{*49@?h&~xo5kY^93Gza zz)$iTlWD&n&b7^xQ?^ZLB*^A7J~#yBizn`|hq9IF;_XnEwf0TOu*&92XaRlc=|!S9 zbJK>qSyY`Pw?q4qGz7=u@YV85LV8H8V zY^pKY#r!!@mz;c6h5|vF<9VETU$&6G1O8_X&O-4482k;-%g>7a7s33r#}}3ew$f{jWCY{Da#oq9!JVig1 z58jlTTX}4LlkTKlF!|h6D<1U}MITIF22Z$S#ISpl7@u-`EHN_ls6);JBwd21X2ysR z`i1h$4{NU6^ETyjtsP{stv_@v51D|@QNiW~CwXY`MZZlhLX-5kL61lidlv>*)0Y8_ zV;c?*bcfZPHU~7rKYiP9XK4;sAT=OWOQHL8veK)BBo*imyMZRs`0uJtFDGaQEVudL z4gU)#Y9KIVHy-_wZQ0bIr$wlhq>ia?p}n@M35U|#2W73;`_C4edI2-C{5NtN z92#{vOoom*Xq0Eo9k~R1F(myPu|hxCy`9!2{wi0lNnQQIgf>oSh$L97zar#vDR@uw zgus_NiSmUcSnsdhiF_&gzA|9UW58QcY?MK8;h+uGhMuVK;!Yu;=i3SCStr!R_EDQ| zcUeBmho?HN0BK{x5@PQ6(V0igYG)0T>rAM+OH7ZM>t>Tdsx@KATM>9RJZ*1gUy$!- zwpwX~dh5j-wLb@l3=aS0ph@1$G2>Dj#3&}h39SeE9AbS5z0_n1mijIze570jx*WYc z1BNpQ^<9^BkHXbF);tK&`_|&?Jn4h=U(0Z{V?Fce4oz;@a`7ZOm2($rMx3E32p8T* zO)6^&tYTYP#8lTldH!{0c%d2}WW;*A1_zO`3-``plaVABOa^Y+$ni}~+f5sdu^;r7 z!vVkS@@++^U~&U;Bzvfh;zVe{+*eja(3fFZVB24NPdTuYZ7 zxK)G`*P|;kGa*DM+Le0(E|dcgdVznnVM8aPYk@lfqW@E=#!nQATwD=sMnQ zw314%#wg!E5+|(S&F)YEWLMgt{sNMz`o6+A5+ zOzcF%w~!Jfm0F;e6rXM&-Iu)y)tJF(mmVuq$sa=IzMA?XwQQpMqj3( zM=^+tXf&}H=Umyicv|AD={rvZkRw?MGF4(x;J-#hCe?%B?g1r+&OJLZ=n->TVy%;ow4dmU7m&gROT)O2*Ls(4okCgF5iNNqqx_~bjn_c3A< z^PLd41W+WQP51I2&S&&;hpAth!*k7?ql%l;(&x3?|c_BejYwjGt0EqO$vx&8}|7uDK{Z*0>^HUdaCMN zzZp=*REIE3lYIr*>c;p43?PGew;UI4_ zv7EWO{m;hJB+4gdu!s}b)R~aAX%=l*8#^t(!wY`}WzoMyFRRwqjp^ne9<$G`z;Q9d`r$9dq`3Uh`kmU#9G2CseM-jb5Q3&%2;~iO zVaFos&@f93dyTm+6_97mGqd754bP&L$O7Ol_WJard(}RYu8|TYKNU-^O9@V4)|4q@ zyXH2FBt)2fD{z>kvGvb6Xi;aeSEJNT`AZd?*`aqv2X_L z9_p6EU;V}IjKjfIMppp75<2a9)F51O(XKkbjw9Ha#rK*NGwN9o8>;Jd_N9P=G8R|z zAADX`Uqv0h;*sT8`oi6rY~dLN8@4V?l&bG#bF8A7v_f6uLnRgG?Cp%NfI6Bd;o12T z015{gCS++hg+MYl5&#M)K=kks-LNNaolvgG*go$H@yzs@OuYnL5mbwPi=dm(i*3M| S4`o#9EX1+mL?p{9ES%sf$F8;j literal 0 HcmV?d00001 diff --git a/qiskit_bot.yaml b/qiskit_bot.yaml index 27335a9e8165..6b74d1784eaa 100644 --- a/qiskit_bot.yaml +++ b/qiskit_bot.yaml @@ -806,6 +806,10 @@ notifications: # "docs/tutorials/implicit-solvent-calculations": # - "`@nathanearnestnoble`" # - "`@blannix`" + "docs/tutorials/quantum-approximate-multi-objective-optimization": + - "`@nathanearnestnoble`" + - "`@kcmccormibm`" + "learning/courses/quantum-computing-in-practice/introduction": - "@livlanes" "learning/courses/quantum-computing-in-practice/running-quantum-circuits": diff --git a/scripts/config/notebook-testing.toml b/scripts/config/notebook-testing.toml index 63a3508e5e1f..830cdea4da91 100644 --- a/scripts/config/notebook-testing.toml +++ b/scripts/config/notebook-testing.toml @@ -207,6 +207,7 @@ notebooks = [ "docs/tutorials/edc-cut-bell-pair-benchmarking.ipynb", "docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits.ipynb", "docs/tutorials/solve-market-split-problem-with-iskay-quantum-optimizer.ipynb", + "docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb", # Don't test any learning notebooks "learning/courses/quantum-computing-in-practice/introduction.ipynb", From 544d8dc19396058476fbc7b73bf34f1accbfc7e4 Mon Sep 17 00:00:00 2001 From: Katie McCormick Date: Tue, 11 Aug 2026 12:00:38 -0700 Subject: [PATCH 2/6] change default backend to any heron device --- ...proximate-multi-objective-optimization.ipynb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb index 2b04c3be55ca..7990ef9eacad 100644 --- a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb +++ b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb @@ -850,7 +850,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "id": "4f5a46b9", "metadata": { "tags": [ @@ -944,17 +944,10 @@ "ansatz_qc_40 = qc_40.assign_parameters(pmap)\n", "\n", "service = QiskitRuntimeService()\n", - "# We pin a specific Heron device instead of service.least_busy(...). least_busy chooses\n", - "# purely on queue depth, and an empty queue is not always a bargain: on Premium/Flex\n", - "# accounts least_busy can route onto ibm_miami, an *exploratory* Nighthawk QPU whose\n", - "# dense square lattice suffers crosstalk that scrambles deep circuits (its operational\n", - "# coherence collapses under load, and a 4 ms default rep-time inflates QPU cost). That\n", - "# is invisible to the calibration-based preflight, so a run can look fine and still fail.\n", - "# marrakesh is a mature heavy-hex Heron; pinning it keeps this tutorial's hardware run\n", - "# reproducible. Swap in another Heron (e.g. ibm_boston, a stronger r3) or uncomment\n", - "# least_busy below if you know your account only sees generally-available devices.\n", - "# backend = service.least_busy(operational=True, simulator=False, min_num_qubits=n_assets_40)\n", - "backend = service.backend(\"ibm_marrakesh\")\n", + "\n", + "# only use Heron devices\n", + "backend = service.least_busy(min_num_qubits=156)\n", + "\n", "\n", "\n", "# SABRE routing is stochastic: different seeds give different depths. Transpilation\n", From 1baa30696266bd896c11cf355172ff06c7244f2a Mon Sep 17 00:00:00 2001 From: Katie McCormick Date: Tue, 11 Aug 2026 12:25:31 -0700 Subject: [PATCH 3/6] change dataset path --- ...um-approximate-multi-objective-optimization.ipynb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb index 7990ef9eacad..f5e9bf80f9cb 100644 --- a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb +++ b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb @@ -664,14 +664,20 @@ }, "outputs": [], "source": [ - "import csv\n", + "import csv, urllib.request\n", "\n", + "# Download the committed market-data snapshot from the repo.\n", "# --- 40-asset universe: 8 GICS sectors x 5 tickers (real market data) ---\n", "# Load the committed market-data snapshot (real annualized returns and covariance).\n", "# Values are stored at the precision used to train the shipped QAOA angles\n", "# (mu: 3 dp, sigma: 4 dp), so the pre-trained parameters in instances/ stay exactly valid.\n", + "\n", + "url = \"https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/qmoo/market_data.csv\"\n", + "urllib.request.urlretrieve(url, \"market_data.csv\")\n", + "\n", "with open(\"market_data.csv\", newline=\"\") as _f:\n", " _rows = list(csv.reader(_f))\n", + "\n", "_tickers_csv = _rows[0][2:] # covariance column order\n", "tickers_40 = [r[0] for r in _rows[1:]] # asset tickers\n", "mu_40 = np.array(\n", @@ -874,8 +880,10 @@ "# Pre-trained angles loaded from a file (training is slow; QDC pattern).\n", "# Set load_params_file = False to retrain in-notebook.\n", "load_params_file = True\n", - "params_path = \"instances/qaoa_params.json\"\n", + "params_url = \"https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/qmoo/qaoa_params.json\"\n", + "params_path = \"qaoa_params.json\"\n", "if load_params_file:\n", + " urllib.request.urlretrieve(params_url, params_path)\n", " qaoa_params = json.load(open(params_path))\n", " p_layers_hw = qaoa_params[\"p_layers\"]\n", " opt_betas_40, opt_gammas_40 = qaoa_params[\"betas\"], qaoa_params[\"gammas\"]\n", From 093d3eea2ea961bef762641db7ede7f6207bda92 Mon Sep 17 00:00:00 2001 From: Katie McCormick Date: Tue, 11 Aug 2026 12:32:31 -0700 Subject: [PATCH 4/6] add params.json file --- datasets/tutorials/{qmoo => qmoo/market_data.csv} | 0 datasets/tutorials/qmoo/qaoa_params.json | 11 +++++++++++ 2 files changed, 11 insertions(+) rename datasets/tutorials/{qmoo => qmoo/market_data.csv} (100%) create mode 100644 datasets/tutorials/qmoo/qaoa_params.json diff --git a/datasets/tutorials/qmoo b/datasets/tutorials/qmoo/market_data.csv similarity index 100% rename from datasets/tutorials/qmoo rename to datasets/tutorials/qmoo/market_data.csv diff --git a/datasets/tutorials/qmoo/qaoa_params.json b/datasets/tutorials/qmoo/qaoa_params.json new file mode 100644 index 000000000000..452ea6c43f13 --- /dev/null +++ b/datasets/tutorials/qmoo/qaoa_params.json @@ -0,0 +1,11 @@ +{ + "p_layers": 1, + "betas": [ + -0.36782342420310354 + ], + "gammas": [ + 0.3167897610520054 + ], + "k_truncate": 2, + "note": "40-asset QAP-truncated risk (k=2) + sector ring, XY mixer. Trained with ScipyTrainer(MPSAerEvaluator, bond 24) on -mean(H_k), matching the small-scale workflow." +} \ No newline at end of file From 64728bf74143a651a542134d52639d023f45633b Mon Sep 17 00:00:00 2001 From: Henry Zou Date: Wed, 19 Aug 2026 15:47:21 -0400 Subject: [PATCH 5/6] tox -e fix --- .../quantum-approximate-multi-objective-optimization.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb index f5e9bf80f9cb..927217977fc9 100644 --- a/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb +++ b/docs/tutorials/quantum-approximate-multi-objective-optimization.ipynb @@ -664,7 +664,8 @@ }, "outputs": [], "source": [ - "import csv, urllib.request\n", + "import csv\n", + "import urllib.request\n", "\n", "# Download the committed market-data snapshot from the repo.\n", "# --- 40-asset universe: 8 GICS sectors x 5 tickers (real market data) ---\n", @@ -957,7 +958,6 @@ "backend = service.least_busy(min_num_qubits=156)\n", "\n", "\n", - "\n", "# SABRE routing is stochastic: different seeds give different depths. Transpilation\n", "# is classical (it costs no QPU time), so we transpile many seeds and keep only the\n", "# shallowest circuit -- a free reduction in two-qubit depth before anything is sent\n", From 47d59ff35b15af8b85f1b9d0b246ff4a6eb0853e Mon Sep 17 00:00:00 2001 From: Henry Zou Date: Wed, 19 Aug 2026 15:56:22 -0400 Subject: [PATCH 6/6] Format qaoa_params.json with prettier --- datasets/tutorials/qmoo/qaoa_params.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/datasets/tutorials/qmoo/qaoa_params.json b/datasets/tutorials/qmoo/qaoa_params.json index 452ea6c43f13..b6980c4d1a8c 100644 --- a/datasets/tutorials/qmoo/qaoa_params.json +++ b/datasets/tutorials/qmoo/qaoa_params.json @@ -1,11 +1,7 @@ { "p_layers": 1, - "betas": [ - -0.36782342420310354 - ], - "gammas": [ - 0.3167897610520054 - ], + "betas": [-0.36782342420310354], + "gammas": [0.3167897610520054], "k_truncate": 2, "note": "40-asset QAP-truncated risk (k=2) + sector ring, XY mixer. Trained with ScipyTrainer(MPSAerEvaluator, bond 24) on -mean(H_k), matching the small-scale workflow." -} \ No newline at end of file +}