Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
118 changes: 78 additions & 40 deletions source/vscode/src/pythonEnvs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,94 +12,138 @@ 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",
picked: true,
},
{
label: "qdk[qiskit]",
packageName: "qdk",
extras: ["qiskit"],
description: "QDK optional support for Qiskit",
detail: "Interop with Qiskit via qdk.qiskit",
picked: false,
},
{
label: "qdk-chemistry",
description: "Microsoft Quantum Development Kit for Chemistry",
detail: "End-to-end toolkit for quantum chemistry",
packageName: "qdk-chemistry",
description: "Microsoft Quantum Development Kit for Chemistry (core)",
detail: "Chemistry library only, without the notebook or PySCF plugins",
picked: false,
},
{
label: "pyscf",
description: "Python-based Simulations of Chemistry Framework",
// The `jupyter` extra pulls in `plugins`, which is where qdk-chemistry
// bounds pyscf. Without it pyscf would be left unconstrained.
label: "qdk-chemistry[jupyter]",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have separate qdk-chemistry and qdk-chemistry[jupyter] entries, as for qdk and then we probably need an aggregator function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. The merge step only looks for qdk today, so chemistry passes straight through. I'll make it group by package name and split the chemistry entries in a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did the split. Also moved the pick items to packageName/extras/versionSpecifier instead of encoding the requirement in the label, and it keeps the version string out of the UI.

packageName: "qdk-chemistry",
extras: ["jupyter"],
description: "QDK/Chemistry optional support for Jupyter notebooks",
detail:
"Collection of electronic structure methods for molecules and periodic solids",
"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<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,
},
];

// pyscf doesn't support Windows
function getAvailablePackagePickItems(): vscode.QuickPickItem[] {
return packagePickItems.filter(
(item) => item.label !== "pyscf" || process.platform !== "win32",
);
}
/**
* 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[] }
>();

// 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);
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<PythonEnvironmentApi | undefined> {
Expand Down Expand Up @@ -212,13 +256,9 @@ 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),
);
const packagesToInstall = toRequirements(selectedPackages);

const existingEnv = await getEnvInFolder(api, root);
if (existingEnv) {
Expand Down Expand Up @@ -296,7 +336,7 @@ export async function createQuantumVenvForCommand(): Promise<void> {
}

const selectedPackages = await vscode.window.showQuickPick(
getAvailablePackagePickItems().map((item) => ({ ...item })),
packagePickItems.map((item) => ({ ...item })),
{
canPickMany: true,
placeHolder: "Select packages to install",
Expand All @@ -306,9 +346,7 @@ export async function createQuantumVenvForCommand(): Promise<void> {
return;
}

const packagesToInstall = coalesceQdkExtras(
selectedPackages.map((item) => item.label),
);
const packagesToInstall = toRequirements(selectedPackages);

try {
if (existingEnv) {
Expand Down