Skip to content
2 changes: 1 addition & 1 deletion docs/api/functions/qunova-chemistry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Whether or not to use configuration [`recovery`](/docs/api/qiskit-addon-sqd/conf

#### `ansatz_entanglement`
<Attribute attributeTypeHint="`str`" attributeValue="`circular`">
This specifies the entanglement scheme that should be used within the quantum circuit, following common Qiskit and [ffsim conventions for the LUCJ ansatz](https://qiskit-community.github.io/ffsim/how-to-guides/simulate-lucj.html).
This specifies the entanglement scheme that should be used within the quantum circuit, following common Qiskit and [ffsim conventions for the LUCJ ansatz](https://qiskit-community.github.io/ffsim/how-to-guides/qiskit-lucj.html).
- Valid range: Any one of `"linear"`, `"reverse_linear"`, `"pairwise"`, `"circular"`, `"full"`, or `"sca"`. If using the `"lucj"` ansatz, `"lucj_default"` is also an option.
</Attribute>

Expand Down
4 changes: 2 additions & 2 deletions docs/guides/multiverse-computing-singularity.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
"Perform the following steps:\n",
"\n",
"1) Create the synthetic dataset using the [`make_moons`](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_moons.html) function from [scikit-learn](https://scikit-learn.org/).\n",
"2) Upload the generated synthetic dataset to the [shared data directory](https://qiskit.github.io/qiskit-serverless/getting_started/experimental/manage_data_directory.html).\n",
"2) Upload the generated synthetic dataset to the [shared data directory](https://qiskit.github.io/qiskit-serverless/function_features/experimental/manage_data_directory.html).\n",
"3) Create the quantum-enhanced classifier using the [`create`](/docs/api/functions/multiverse-computing-singularity#create) action.\n",
"4) Enlist your classifiers using the [`list`](/docs/api/functions/multiverse-computing-singularity#list) action.\n",
"5) Train the classifier on the train data using the [`fit`](/docs/api/functions/multiverse-computing-singularity#fit) action.\n",
Expand Down Expand Up @@ -247,7 +247,7 @@
"id": "639f459f",
"metadata": {},
"source": [
"**Step 2.** Save the labeled training and test datasets on your local disk, and then upload them to the [shared data directory](https://qiskit.github.io/qiskit-serverless/getting_started/experimental/manage_data_directory.html)."
"**Step 2.** Save the labeled training and test datasets on your local disk, and then upload them to the [shared data directory](https://qiskit.github.io/qiskit-serverless/function_features/experimental/manage_data_directory.html)."
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion docs/responsible-quantum-computing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ Finally, as part of our responsible quantum effort, we are committed to fosterin
<Admonition type="tip" title="Learn more">
- [IBM Quantum Safe](https://www.ibm.com/quantum/quantum-safe)
- [Blog: The era of quantum utility must also be the era of responsible quantum computing](https://www.ibm.com/quantum/blog/responsible-quantum)
- [IBM Research Responsible and Inclusive Tech](https://research.ibm.com/projects/responsible-and-inclusive-tech-research)
- [Topic: Responsible Technology (IBM Research)](https://research.ibm.com/topics/responsible-technology)
</Admonition>
2 changes: 1 addition & 1 deletion docs/tutorials/quantum-kernel-training.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@
"source": [
"## Deploy the Qiskit pattern to the cloud\n",
"\n",
"To do this, move the source code above to a file, `./source/generate_kernel_entry.py`, wrap the code in a script which takes inputs returns the final solution, and finally upload it to a remote cluster using the `QiskitFunction` class from `Qiskit Serverless`. For guidance on specifying external dependencies, passing input arguments, and more, check out the [Qiskit Serverless guides](https://qiskit.github.io/qiskit-serverless/getting_started/index.html).\n",
"To do this, move the source code above to a file, `./source/generate_kernel_entry.py`, wrap the code in a script which takes inputs returns the final solution, and finally upload it to a remote cluster using the `QiskitFunction` class from `Qiskit Serverless`. For guidance on specifying external dependencies, passing input arguments, and more, check out the [Qiskit Serverless guides](https://qiskit.github.io/qiskit-serverless/function_features/index.html#function-features).\n",
"\n",
"The input to the Pattern is a pair of data samples, `x1` and `x2`. The output is the fidelity between the two samples. This value will be used to populate the kernel matrix entry corresponding to these two samples."
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ Review the resources listed here to learn more about how IBM Quantum computers a

As part of its decarbonization efforts, E.ON has partnered with IBM to explore the potential of quantum computing to [optimize the world’s increasingly decentralized energy infrastructure](https://www.eon.com/en/about-us/media/press-release/2021/2021-09-02-eon-allies-with-ibm-quantum.html). “You plug in your electric car to charge the battery, and you might have a solar panel that powers your house and car. But can you sell that excess energy to your neighbors down the road? Why do you have to get energy from thousands of kilometers away that was made in the power plant burning gas?” asks Corey O’Meara, E.ON digital technology quantum computing lead (see [“IBM Panel Highlights Quantum Role in Sustainability”](https://www.iotworldtoday.com/smart-cities/ibm-panel-highlights-quantum-role-in-sustainability)). Quantum computing algorithms could hold the key to managing the complexity that results when additional assets are plugged into the grid.

The potential for quantum computing to aid in the discovery of new materials designed to improve the generation, transfer, and storage of energy is one reason why bp is allying with IBM Quantum to [achieve its net-zero goals](https://newsroom.ibm.com/2021-02-15-bp-joins-the-IBM-Quantum-Network-to-advance-use-of-quantum-computing-in-energy).

Woodside Energy, an IBM partner, is experimenting with new algorithms to reduce the overhead of data transfers between classical and quantum systems, making it possible to [apply quantum kernels to streaming data](https://arxiv.org/abs/2112.08449).

In the telecommunications industry, quantum computing shows potential to deliver solutions for network traffic routing and workload balancing, GHG/energy consumption, and contextual customer segmentation. Vodafone is partnering with IBM Quantum to help [validate and progress potential quantum use cases in telecommunications](https://newsroom.ibm.com/2022-11-09-IBM-and-Vodafone-Join-Forces-in-Exploration-of-Quantum-Computing-Technology-and-Quantum-Safe-Cryptography).
Expand Down
2 changes: 1 addition & 1 deletion learning/courses/quantum-safe-cryptography/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ PDFs of lecture notes will be available soon
- [Fundamentals of quantum algorithms](/learning/courses/fundamentals-of-quantum-algorithms/index), which explores computational advantages of quantum systems, and includes further details on algorithms such as Grover's algorithm
- [IBM Quantum Safe&trade;](https://www.ibm.com/quantum/quantum-safe)
- [Why it’s time to take quantum-safe cryptography seriously](https://research.ibm.com/blog/quantum-safe-cryptography-for-industry)
- [Fundamentals of Encryption & Quantum-Safe Techniques](https://catalog.skills.network/2766)
- [Fundamentals of Encryption & Quantum-Safe Techniques](https://catalog.skills.network/catalog_item/2766/)

## Exam

Expand Down
2 changes: 1 addition & 1 deletion learning/courses/quantum-safe-cryptography/works-cited.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,4 @@ description: This document provides a list of all citations used within the cour

[135] Fujisaki, Eiichiro, and Okamato, Tatsuaki, "Secure Integration of Symmetric and Asymmetric Encryption Schemes," Journal of Crytography, pp. 80-101, Jan. 2013, doi: [10.1007/s00145-011-9114-1](https://doi.org/10.1007/s00145-011-9114-1)

[136] Snow, Dwaine, "Fundamentals of Encryption & Quantum-Safe Techniques," Skills Network. [Online]. Available: [https://catalog.skills.network/2766](https://catalog.skills.network/2766)
[136] Snow, Dwaine, "Fundamentals of Encryption & Quantum-Safe Techniques," Skills Network. [Online]. Available: [https://catalog.skills.network/catalog_item/2766/](https://catalog.skills.network/catalog_item/2766/)
122 changes: 117 additions & 5 deletions scripts/js/lib/links/ExternalLink.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ExternalLink } from "./ExternalLink.js";

test("ExternalLink constructor ignores anchors", () => {
const link = new ExternalLink("https://ibm.com#my-anchor", []);
expect(link.value).toEqual("https://ibm.com");
expect(link.value).toEqual("https://ibm.com/");
});

test.describe("ExternalLink.check()", () => {
Expand All @@ -42,7 +42,7 @@ test.describe("ExternalLink.check()", () => {
let link = new ExternalLink("https://bad-link.com", ["/testorigin.mdx"]);
const result = await link.check();
expect(result).toEqual(
"❌ Could not find link 'https://bad-link.com' (404). Appears in:\n /testorigin.mdx",
"❌ Could not find link 'https://bad-link.com/' (404). Appears in:\n /testorigin.mdx",
);
});

Expand All @@ -51,7 +51,7 @@ test.describe("ExternalLink.check()", () => {
let link = new ExternalLink("https://bad-link.com", ["/testorigin.mdx"]);
const result = await link.check();
expect(result).toEqual(
"❌ Link 'https://bad-link.com' has been removed (410). Appears in:\n /testorigin.mdx",
"❌ Link 'https://bad-link.com/' has been removed (410). Appears in:\n /testorigin.mdx",
);
});

Expand All @@ -60,7 +60,7 @@ test.describe("ExternalLink.check()", () => {
let link = new ExternalLink("https://bad-link.com", ["/testorigin.mdx"]);
const result = await link.check();
expect(result).toEqual(
"❌ Link 'https://bad-link.com' returned unexpected code: 502. Appears in:\n /testorigin.mdx",
"❌ Link 'https://bad-link.com/' returned unexpected code: 502. Appears in:\n /testorigin.mdx",
);
});

Expand All @@ -69,7 +69,119 @@ test.describe("ExternalLink.check()", () => {
let link = new ExternalLink("https://bad-link.com", ["/testorigin.mdx"]);
const result = await link.check();
expect(result).toEqual(
"❌ Failed to fetch 'https://bad-link.com': some issue. Appears in:\n /testorigin.mdx",
"❌ Failed to fetch 'https://bad-link.com/': some issue. Appears in:\n /testorigin.mdx",
);
});

test("301 redirect is considered valid", async () => {
global.fetch = () => Promise.resolve(new Response("", { status: 301 }));
let link = new ExternalLink("https://redirect-link.com", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
});

test("302 redirect is considered valid", async () => {
global.fetch = () => Promise.resolve(new Response("", { status: 302 }));
let link = new ExternalLink("https://redirect-link.com", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
});

test("403 Forbidden is considered valid", async () => {
global.fetch = () => Promise.resolve(new Response("", { status: 403 }));
let link = new ExternalLink("https://forbidden-link.com", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
});

test("429 Too Many Requests is considered valid", async () => {
global.fetch = () => Promise.resolve(new Response("", { status: 429 }));
let link = new ExternalLink("https://rate-limited-link.com", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
});

test("405 Method Not Allowed triggers GET fallback", async () => {
let callCount = 0;
global.fetch = (url: any, options: any) => {
callCount++;
if (callCount === 1) {
// First call (HEAD) returns 405
expect(options.method).toBe("HEAD");
return Promise.resolve(new Response("", { status: 405 }));
} else {
// Second call (GET) returns 200
expect(options.method).toBe("GET");
return Promise.resolve(new Response("", { status: 200 }));
}
};
let link = new ExternalLink("https://pdf-link.com/file.pdf", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
expect(callCount).toBe(2); // Verify both HEAD and GET were called
});

test("501 Not Implemented triggers GET fallback", async () => {
let callCount = 0;
global.fetch = (url: any, options: any) => {
callCount++;
if (callCount === 1) {
// First call (HEAD) returns 501
expect(options.method).toBe("HEAD");
return Promise.resolve(new Response("", { status: 501 }));
} else {
// Second call (GET) returns 200
expect(options.method).toBe("GET");
return Promise.resolve(new Response("", { status: 200 }));
}
};
let link = new ExternalLink("https://binary-link.com/file.zip", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toBeUndefined();
expect(callCount).toBe(2);
});

test("URL with escape characters is handled correctly", async () => {
global.fetch = (url: any) => {
// Verify the URL is properly normalized
expect(url).toContain("%20");
return Promise.resolve(new Response("", { status: 200 }));
};
let link = new ExternalLink(
"https://example.com/file%20with%20spaces.pdf",
["/testorigin.mdx"],
);
const result = await link.check();
expect(result).toBeUndefined();
});

test("405 followed by GET failure still reports error", async () => {
let callCount = 0;
global.fetch = (url: any, options: any) => {
callCount++;
if (callCount === 1) {
return Promise.resolve(new Response("", { status: 405 }));
} else {
return Promise.resolve(new Response("", { status: 404 }));
}
};
let link = new ExternalLink("https://bad-pdf.com/missing.pdf", [
"/testorigin.mdx",
]);
const result = await link.check();
expect(result).toContain("Could not find link");
expect(result).toContain("404");
});
});
44 changes: 37 additions & 7 deletions scripts/js/lib/links/ExternalLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,30 @@ export class ExternalLink {
);
}

// We strip anchors.
this.value = linkString.split("#", 2)[0];
// Normalize URL to handle escape characters, then strip anchors
try {
const normalized = new URL(linkString).href;
this.value = normalized.split("#", 2)[0];
} catch {
// If URL parsing fails, fall back to original behavior
this.value = linkString.split("#", 2)[0];
}
this.originFiles = new Set(originFiles);
}

/**
* Returns an error message if link failed.
*/
async check(): Promise<string | undefined> {
const result = await safeFetch(this.value);
// Try HEAD request first (faster, less bandwidth)
let result = await safeFetch(this.value, "HEAD");

// If HEAD returns 405 (Method Not Allowed) or 501 (Not Implemented),
// retry with GET as many servers don't support HEAD for PDFs and binary files
if ("response" in result && shouldRetryWithGet(result.response.status)) {
result = await safeFetch(this.value, "GET");
}

const error =
"response" in result
? responseToErrorMessage(this.value, result.response)
Expand All @@ -53,11 +67,18 @@ export class ExternalLink {

async function safeFetch(
link: string,
method: "HEAD" | "GET" = "HEAD",
): Promise<{ response: Response } | { error: Error }> {
try {
const response = await fetch(link, {
// Normalize URL to handle encoding issues
const normalizedUrl = new URL(link).href;

const response = await fetch(normalizedUrl, {
headers: getHeaders(link),
method: "HEAD",
method: method,
// For GET requests, we only need headers to check if link is valid
// Using signal to abort after receiving headers saves bandwidth
signal: method === "GET" ? AbortSignal.timeout(10000) : undefined,
});
return { response };
} catch (err) {
Expand All @@ -70,16 +91,25 @@ function responseToErrorMessage(
response: Response,
): string | undefined {
const httpCode = response.status;
const isOk = httpCode >= 100 && httpCode < 300;

// Accept 1xx-3xx as valid (including redirects)
// Accept 403 as potentially valid (resource exists but access is forbidden)
// Accept 429 as valid (resource exists but we're being rate limited)
const isOk =
(httpCode >= 100 && httpCode < 400) || httpCode === 403 || httpCode === 429;
if (isOk) return undefined;

if (httpCode === 404) return `Could not find link '${link}' (${httpCode})`;
if (httpCode === 410) return `Link '${link}' has been removed (${httpCode})`;
if (httpCode === 418) return `Link '${link}' is a teapot (${httpCode})`;

return `Link '${link}' returned unexpected code: ${httpCode}`;
}

function shouldRetryWithGet(status: number): boolean {
// Retry with GET when HEAD is not supported
return status === 405 || status === 501;
}

export function getHeaders(link: string): HeadersInit {
const headers: HeadersInit = {
"User-Agent": "qiskit-documentation-scripts",
Expand Down
Loading
Loading