From bc57ebddcef3f92478750cc2acf7a1b5b2a65e06 Mon Sep 17 00:00:00 2001 From: HABER7789 Date: Fri, 28 Aug 2026 12:10:27 -0700 Subject: [PATCH 1/3] Let qdk-chemistry supply pyscf in the venv command --- .../courses/chemistry-qpe/requirements.txt | 1 - source/vscode/src/pythonEnvs.ts | 26 +++++-------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/source/vscode/resources/qdk-learning/courses/chemistry-qpe/requirements.txt b/source/vscode/resources/qdk-learning/courses/chemistry-qpe/requirements.txt index a750be746ef..8b2c623ac15 100644 --- a/source/vscode/resources/qdk-learning/courses/chemistry-qpe/requirements.txt +++ b/source/vscode/resources/qdk-learning/courses/chemistry-qpe/requirements.txt @@ -2,4 +2,3 @@ qdk-chemistry[jupyter]>=2.1.0 # Pinned to 6.x: ipykernel 7 can leave notebooks hanging on the first cell. # Remove once https://github.com/microsoft/qdk/issues/3662 is fixed. ipykernel>=6.0,<7 -pyscf>=2.9.0,<2.12.1 diff --git a/source/vscode/src/pythonEnvs.ts b/source/vscode/src/pythonEnvs.ts index a25242c8ecb..a2bfc4f55ab 100644 --- a/source/vscode/src/pythonEnvs.ts +++ b/source/vscode/src/pythonEnvs.ts @@ -46,22 +46,17 @@ const packagePickItems: vscode.QuickPickItem[] = [ picked: false, }, { - label: "qdk-chemistry", + // qdk-chemistry bounds pyscf under its `plugins` extra, which `jupyter` + // pulls in. Installing it bare would leave pyscf unconstrained. + label: "qdk-chemistry[jupyter]", description: "Microsoft Quantum Development Kit for Chemistry", detail: "End-to-end toolkit for quantum chemistry", picked: false, }, - { - label: "pyscf", - description: "Python-based Simulations of Chemistry Framework", - detail: - "Collection of electronic structure methods for molecules and periodic solids", - picked: false, - }, { // Pinned to 6.x: ipykernel 7 can leave notebooks hanging on the first cell. // Remove once https://github.com/microsoft/qdk/issues/3662 is fixed. - label: "ipykernel<7", + label: "ipykernel>=6.0,<7", description: "Jupyter kernel", detail: "Enable Jupyter notebook functionality in VS Code", picked: true, @@ -74,13 +69,6 @@ const packagePickItems: vscode.QuickPickItem[] = [ }, ]; -// pyscf doesn't support Windows -function getAvailablePackagePickItems(): vscode.QuickPickItem[] { - return packagePickItems.filter( - (item) => item.label !== "pyscf" || process.platform !== "win32", - ); -} - // Merge selected qdk extras (e.g. qdk + qdk[azure] + qdk[jupyter]) into one specifier. function coalesceQdkExtras(packages: string[]): string[] { const extras: string[] = []; @@ -212,9 +200,7 @@ export async function createQuantumVenv(): Promise<{ action: string }> { } // Don't interrupt the chat by showing a picker - just use the defaults - const selectedPackages = getAvailablePackagePickItems().filter( - (item) => item.picked, - ); + const selectedPackages = packagePickItems.filter((item) => item.picked); const packagesToInstall = coalesceQdkExtras( selectedPackages.map((item) => item.label), @@ -296,7 +282,7 @@ export async function createQuantumVenvForCommand(): Promise { } const selectedPackages = await vscode.window.showQuickPick( - getAvailablePackagePickItems().map((item) => ({ ...item })), + packagePickItems.map((item) => ({ ...item })), { canPickMany: true, placeHolder: "Select packages to install", From c067b029d8a343acf2eb4e312df8d601932bfd1e Mon Sep 17 00:00:00 2001 From: HABER7789 Date: Mon, 31 Aug 2026 11:15:12 -0700 Subject: [PATCH 2/3] Split the chemistry picker entries and group requirements by package - Pick items now carry packageName, extras, and versionSpecifier - selections are merged by package name - the version constraint no longer shows in the picker --- source/vscode/src/pythonEnvs.ts | 110 +++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/source/vscode/src/pythonEnvs.ts b/source/vscode/src/pythonEnvs.ts index a2bfc4f55ab..9a4c4497d14 100644 --- a/source/vscode/src/pythonEnvs.ts +++ b/source/vscode/src/pythonEnvs.ts @@ -12,28 +12,49 @@ import { CopilotToolError } from "./gh-copilot/types.js"; const pythonEnvsNotInstalledMsg = `The Python Environments extension (${EXTENSION_ID}) is not installed or is disabled.`; +/** + * A package offered in the venv picker. `label` is display text only; the + * requirement passed to pip is built from the fields below, so a version + * constraint never leaks into the UI. + */ +interface PackagePickItem extends vscode.QuickPickItem { + /** Distribution name, e.g. `qdk-chemistry`. */ + packageName: string; + /** Extras to request, e.g. `["jupyter"]`. */ + extras?: string[]; + /** Version constraint, e.g. `>=6.0,<7`. */ + versionSpecifier?: string; +} + // All packages offered in the command palette picker (in display order) -const packagePickItems: vscode.QuickPickItem[] = [ +const packagePickItems: PackagePickItem[] = [ { label: "qdk", + packageName: "qdk", description: "Quantum Development Kit (core)", detail: "Compile, simulate, and estimate resources for quantum programs", picked: true, }, { label: "qdk[azure]", + packageName: "qdk", + extras: ["azure"], description: "QDK optional support for Azure Quantum", detail: "Submit jobs to Azure Quantum hardware and cloud simulators", picked: false, }, { label: "qdk[cirq]", + packageName: "qdk", + extras: ["cirq"], description: "QDK optional support for Cirq", detail: "Interop with Cirq via qdk.cirq", picked: false, }, { label: "qdk[jupyter]", + packageName: "qdk", + extras: ["jupyter"], description: "QDK optional support for Jupyter notebooks", detail: "Enable Q# code cells and interactive quantum widgets in Jupyter notebooks", @@ -41,53 +62,88 @@ const packagePickItems: vscode.QuickPickItem[] = [ }, { label: "qdk[qiskit]", + packageName: "qdk", + extras: ["qiskit"], description: "QDK optional support for Qiskit", detail: "Interop with Qiskit via qdk.qiskit", picked: false, }, { - // qdk-chemistry bounds pyscf under its `plugins` extra, which `jupyter` - // pulls in. Installing it bare would leave pyscf unconstrained. + label: "qdk-chemistry", + packageName: "qdk-chemistry", + description: "Microsoft Quantum Development Kit for Chemistry (core)", + detail: "Chemistry library only, without the notebook or PySCF plugins", + picked: false, + }, + { + // The `jupyter` extra pulls in `plugins`, which is where qdk-chemistry + // bounds pyscf. Without it pyscf would be left unconstrained. label: "qdk-chemistry[jupyter]", - description: "Microsoft Quantum Development Kit for Chemistry", - detail: "End-to-end toolkit for quantum chemistry", + packageName: "qdk-chemistry", + extras: ["jupyter"], + description: "QDK/Chemistry optional support for Jupyter notebooks", + detail: + "Add the notebook and simulation plugins, including PySCF. Required by the chemistry course.", picked: false, }, { + label: "ipykernel", + packageName: "ipykernel", // Pinned to 6.x: ipykernel 7 can leave notebooks hanging on the first cell. // Remove once https://github.com/microsoft/qdk/issues/3662 is fixed. - label: "ipykernel>=6.0,<7", + versionSpecifier: ">=6.0,<7", description: "Jupyter kernel", detail: "Enable Jupyter notebook functionality in VS Code", picked: true, }, { label: "ipympl", + packageName: "ipympl", description: "Interactive Matplotlib widgets", detail: "Enable interactive plots in Jupyter notebooks", picked: true, }, ]; -// Merge selected qdk extras (e.g. qdk + qdk[azure] + qdk[jupyter]) into one specifier. -function coalesceQdkExtras(packages: string[]): string[] { - const extras: string[] = []; - const rest: string[] = []; - let hasQdk = false; - for (const pkg of packages) { - if (pkg === "qdk") { - hasQdk = true; - } else if (pkg.startsWith("qdk[") && pkg.endsWith("]")) { - hasQdk = true; - extras.push(...pkg.slice(4, -1).split(",")); - } else { - rest.push(pkg); +/** + * Build the pip requirements for the selected items, merging selections that + * name the same package so ticking `qdk` and `qdk[jupyter]` installs + * `qdk[jupyter]` rather than passing both. Selection order is preserved. + */ +function toRequirements(selected: readonly PackagePickItem[]): string[] { + const order: string[] = []; + const byName = new Map< + string, + { extras: string[]; versionSpecifiers: string[] } + >(); + + for (const item of selected) { + let merged = byName.get(item.packageName); + if (!merged) { + merged = { extras: [], versionSpecifiers: [] }; + byName.set(item.packageName, merged); + order.push(item.packageName); + } + for (const extra of item.extras ?? []) { + if (!merged.extras.includes(extra)) { + merged.extras.push(extra); + } + } + // Constraints from every selected row are kept, so pinning two rows of the + // same package narrows the range instead of silently dropping one. + if ( + item.versionSpecifier && + !merged.versionSpecifiers.includes(item.versionSpecifier) + ) { + merged.versionSpecifiers.push(item.versionSpecifier); } } - if (hasQdk) { - rest.unshift(extras.length > 0 ? `qdk[${extras.join(",")}]` : "qdk"); - } - return rest; + + return order.map((name) => { + const { extras, versionSpecifiers } = byName.get(name)!; + const extrasPart = extras.length > 0 ? `[${extras.join(",")}]` : ""; + return `${name}${extrasPart}${versionSpecifiers.join(",")}`; + }); } async function getPythonEnvsApi(): Promise { @@ -202,9 +258,7 @@ export async function createQuantumVenv(): Promise<{ action: string }> { // Don't interrupt the chat by showing a picker - just use the defaults const selectedPackages = packagePickItems.filter((item) => item.picked); - const packagesToInstall = coalesceQdkExtras( - selectedPackages.map((item) => item.label), - ); + const packagesToInstall = toRequirements(selectedPackages); const existingEnv = await getEnvInFolder(api, root); if (existingEnv) { @@ -292,9 +346,7 @@ export async function createQuantumVenvForCommand(): Promise { return; } - const packagesToInstall = coalesceQdkExtras( - selectedPackages.map((item) => item.label), - ); + const packagesToInstall = toRequirements(selectedPackages); try { if (existingEnv) { From 8691f21d1be5e5744ec11a6234e5fc1828ed0ba0 Mon Sep 17 00:00:00 2001 From: HABER7789 Date: Mon, 31 Aug 2026 15:46:00 -0700 Subject: [PATCH 3/3] Compute picker labels to remove duplication --- source/vscode/src/pythonEnvs.ts | 65 +++++++++++++++++---------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/source/vscode/src/pythonEnvs.ts b/source/vscode/src/pythonEnvs.ts index 9a4c4497d14..c2352cdc342 100644 --- a/source/vscode/src/pythonEnvs.ts +++ b/source/vscode/src/pythonEnvs.ts @@ -13,30 +13,43 @@ import { CopilotToolError } from "./gh-copilot/types.js"; const pythonEnvsNotInstalledMsg = `The Python Environments extension (${EXTENSION_ID}) is not installed or is disabled.`; /** - * A package offered in the venv picker. `label` is display text only; the - * requirement passed to pip is built from the fields below, so a version - * constraint never leaks into the UI. + * A package offered in the venv picker. This is plain data: the pip + * requirement and the label shown to the learner are both derived from these + * fields, so they can't drift apart, and a version constraint never leaks + * into the UI. Converted to a {@link vscode.QuickPickItem} only when shown. */ -interface PackagePickItem extends vscode.QuickPickItem { +interface PackageChoice { /** Distribution name, e.g. `qdk-chemistry`. */ packageName: string; /** Extras to request, e.g. `["jupyter"]`. */ extras?: string[]; - /** Version constraint, e.g. `>=6.0,<7`. */ + /** Version constraint, e.g. `>=6.0,<7`. Not shown in the picker. */ versionSpecifier?: string; + /** Short text shown beside the label. */ + description: string; + /** Longer text shown under the label. */ + detail: string; + /** Whether the item is checked by default. */ + picked?: boolean; +} + +/** `qdk` plus `["azure"]` renders as `qdk[azure]`. */ +function packageLabel(choice: PackageChoice): string { + const extras = choice.extras ?? []; + return extras.length > 0 + ? `${choice.packageName}[${extras.join(",")}]` + : choice.packageName; } // All packages offered in the command palette picker (in display order) -const packagePickItems: PackagePickItem[] = [ +const packageChoices: PackageChoice[] = [ { - label: "qdk", packageName: "qdk", description: "Quantum Development Kit (core)", detail: "Compile, simulate, and estimate resources for quantum programs", picked: true, }, { - label: "qdk[azure]", packageName: "qdk", extras: ["azure"], description: "QDK optional support for Azure Quantum", @@ -44,7 +57,6 @@ const packagePickItems: PackagePickItem[] = [ picked: false, }, { - label: "qdk[cirq]", packageName: "qdk", extras: ["cirq"], description: "QDK optional support for Cirq", @@ -52,7 +64,6 @@ const packagePickItems: PackagePickItem[] = [ picked: false, }, { - label: "qdk[jupyter]", packageName: "qdk", extras: ["jupyter"], description: "QDK optional support for Jupyter notebooks", @@ -61,7 +72,6 @@ const packagePickItems: PackagePickItem[] = [ picked: true, }, { - label: "qdk[qiskit]", packageName: "qdk", extras: ["qiskit"], description: "QDK optional support for Qiskit", @@ -69,7 +79,6 @@ const packagePickItems: PackagePickItem[] = [ picked: false, }, { - label: "qdk-chemistry", packageName: "qdk-chemistry", description: "Microsoft Quantum Development Kit for Chemistry (core)", detail: "Chemistry library only, without the notebook or PySCF plugins", @@ -78,7 +87,6 @@ const packagePickItems: PackagePickItem[] = [ { // The `jupyter` extra pulls in `plugins`, which is where qdk-chemistry // bounds pyscf. Without it pyscf would be left unconstrained. - label: "qdk-chemistry[jupyter]", packageName: "qdk-chemistry", extras: ["jupyter"], description: "QDK/Chemistry optional support for Jupyter notebooks", @@ -87,7 +95,6 @@ const packagePickItems: PackagePickItem[] = [ picked: false, }, { - label: "ipykernel", packageName: "ipykernel", // Pinned to 6.x: ipykernel 7 can leave notebooks hanging on the first cell. // Remove once https://github.com/microsoft/qdk/issues/3662 is fixed. @@ -97,7 +104,6 @@ const packagePickItems: PackagePickItem[] = [ picked: true, }, { - label: "ipympl", packageName: "ipympl", description: "Interactive Matplotlib widgets", detail: "Enable interactive plots in Jupyter notebooks", @@ -110,39 +116,31 @@ const packagePickItems: PackagePickItem[] = [ * name the same package so ticking `qdk` and `qdk[jupyter]` installs * `qdk[jupyter]` rather than passing both. Selection order is preserved. */ -function toRequirements(selected: readonly PackagePickItem[]): string[] { - const order: string[] = []; +function toRequirements(selected: readonly PackageChoice[]): string[] { const byName = new Map< string, - { extras: string[]; versionSpecifiers: string[] } + { extras: string[]; versionSpecifier: string } >(); for (const item of selected) { let merged = byName.get(item.packageName); if (!merged) { - merged = { extras: [], versionSpecifiers: [] }; + merged = { extras: [], versionSpecifier: "" }; byName.set(item.packageName, merged); - order.push(item.packageName); } for (const extra of item.extras ?? []) { if (!merged.extras.includes(extra)) { merged.extras.push(extra); } } - // Constraints from every selected row are kept, so pinning two rows of the - // same package narrows the range instead of silently dropping one. - if ( - item.versionSpecifier && - !merged.versionSpecifiers.includes(item.versionSpecifier) - ) { - merged.versionSpecifiers.push(item.versionSpecifier); + if (item.versionSpecifier) { + merged.versionSpecifier = item.versionSpecifier; } } - return order.map((name) => { - const { extras, versionSpecifiers } = byName.get(name)!; + return [...byName].map(([name, { extras, versionSpecifier }]) => { const extrasPart = extras.length > 0 ? `[${extras.join(",")}]` : ""; - return `${name}${extrasPart}${versionSpecifiers.join(",")}`; + return `${name}${extrasPart}${versionSpecifier}`; }); } @@ -256,7 +254,7 @@ export async function createQuantumVenv(): Promise<{ action: string }> { } // Don't interrupt the chat by showing a picker - just use the defaults - const selectedPackages = packagePickItems.filter((item) => item.picked); + const selectedPackages = packageChoices.filter((item) => item.picked); const packagesToInstall = toRequirements(selectedPackages); @@ -336,7 +334,10 @@ export async function createQuantumVenvForCommand(): Promise { } const selectedPackages = await vscode.window.showQuickPick( - packagePickItems.map((item) => ({ ...item })), + packageChoices.map((choice) => ({ + ...choice, + label: packageLabel(choice), + })), { canPickMany: true, placeHolder: "Select packages to install",