diff --git a/docs/api/functions/qunova-chemistry.mdx b/docs/api/functions/qunova-chemistry.mdx index 1463cb7dd15..3085269ec2f 100644 --- a/docs/api/functions/qunova-chemistry.mdx +++ b/docs/api/functions/qunova-chemistry.mdx @@ -198,7 +198,7 @@ Whether or not to use configuration [`recovery`](/docs/api/qiskit-addon-sqd/conf #### `ansatz_entanglement` -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. diff --git a/docs/guides/multiverse-computing-singularity.ipynb b/docs/guides/multiverse-computing-singularity.ipynb index 601259fc336..1e5d2432165 100644 --- a/docs/guides/multiverse-computing-singularity.ipynb +++ b/docs/guides/multiverse-computing-singularity.ipynb @@ -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", @@ -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)." ] }, { diff --git a/docs/responsible-quantum-computing.mdx b/docs/responsible-quantum-computing.mdx index b63d1b5c45c..dfb0f28df6b 100644 --- a/docs/responsible-quantum-computing.mdx +++ b/docs/responsible-quantum-computing.mdx @@ -38,5 +38,5 @@ Finally, as part of our responsible quantum effort, we are committed to fosterin - [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) \ No newline at end of file diff --git a/docs/tutorials/quantum-kernel-training.ipynb b/docs/tutorials/quantum-kernel-training.ipynb index 2d07485ef08..9ca5223336d 100644 --- a/docs/tutorials/quantum-kernel-training.ipynb +++ b/docs/tutorials/quantum-kernel-training.ipynb @@ -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." ] diff --git a/learning/courses/quantum-business-foundations/business-impacts.mdx b/learning/courses/quantum-business-foundations/business-impacts.mdx index ee79245e713..5e631d9ca96 100644 --- a/learning/courses/quantum-business-foundations/business-impacts.mdx +++ b/learning/courses/quantum-business-foundations/business-impacts.mdx @@ -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). diff --git a/learning/courses/quantum-safe-cryptography/index.mdx b/learning/courses/quantum-safe-cryptography/index.mdx index f00763d7ebd..72e237ec322 100644 --- a/learning/courses/quantum-safe-cryptography/index.mdx +++ b/learning/courses/quantum-safe-cryptography/index.mdx @@ -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™](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 diff --git a/learning/courses/quantum-safe-cryptography/works-cited.mdx b/learning/courses/quantum-safe-cryptography/works-cited.mdx index 21ca6c7416a..e130780b33a 100644 --- a/learning/courses/quantum-safe-cryptography/works-cited.mdx +++ b/learning/courses/quantum-safe-cryptography/works-cited.mdx @@ -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/) diff --git a/scripts/js/lib/links/ExternalLink.test.ts b/scripts/js/lib/links/ExternalLink.test.ts index 2b72fcd6ccc..0239cd9a3b4 100644 --- a/scripts/js/lib/links/ExternalLink.test.ts +++ b/scripts/js/lib/links/ExternalLink.test.ts @@ -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()", () => { @@ -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", ); }); @@ -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", ); }); @@ -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", ); }); @@ -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"); + }); }); diff --git a/scripts/js/lib/links/ExternalLink.ts b/scripts/js/lib/links/ExternalLink.ts index 98c0866e312..6dfb70a442c 100644 --- a/scripts/js/lib/links/ExternalLink.ts +++ b/scripts/js/lib/links/ExternalLink.ts @@ -25,8 +25,14 @@ 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); } @@ -34,7 +40,15 @@ export class ExternalLink { * Returns an error message if link failed. */ async check(): Promise { - 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) @@ -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) { @@ -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", diff --git a/scripts/js/lib/links/ignores.ts b/scripts/js/lib/links/ignores.ts index 622897ab8cf..862c81c69ea 100644 --- a/scripts/js/lib/links/ignores.ts +++ b/scripts/js/lib/links/ignores.ts @@ -34,6 +34,7 @@ const FORBIDS_OUR_USER_AGENT = [ "http://dx.doi.org/10.1145/1278349.1278355", "https://academic.oup.com/book/36426", "https://crates.io/crates/faer", + "https://crates.io/crates/log", "https://dl.acm.org/doi/10.1145/237814.237838", "https://dl.acm.org/doi/10.1145/237814.237866", "https://dl.acm.org/doi/10.1145/3445814.3446706", @@ -124,10 +125,16 @@ const FETCH_RETURNS_405 = [ // Fetching these links throws an error, but they work in-browser. Not sure why. const FETCH_FAILS = [ + "https://sphincs.org/", "https://www.cs.tau.ac.il/~nogaa/PDFS/r.pdf", + "https://www.cs.tau.ac.il/\\~nogaa/PDFS/r.pdf", // Escaped version from markdown + "https://www.epo.org/en/news-events/press-centre/press-release/2025/1361562", "https://www.mckinsey.com/business-functions/mckinsey-digital/our-insights/quantum-computing-use-cases-are-getting-real-what-you-need-to-know", "https://www.mckinsey.com/capabilities/mckinsey-digital/our-insights/quantum-computing-just-might-save-the-planet", + "https://www.mckinsey.com/capabilities/mckinsey-technology/our-insights/solving-chemistrys-toughest-problems-the-quantum-computing-advantage", "https://www.mckinsey.com/industries/chemicals/our-insights/the-next-big-thing-quantum-computings-potential-impact-on-chemicals?cid=eml-web", + "https://www.mckinsey.com/capabilities/tech-and-ai/our-insights/the-year-of-quantum-from-concept-to-reality-in-2025", + "https://thequantuminsider.com/2024/10/12/ibm-quantum-roadmap-guide-scaling-and-expanding-the-usefulness-of-quantum-computing/", // The following link is only accessible through IBM VPN "https://w3.ibm.com/w3publisher/w3-privacy-notice", ]; @@ -142,7 +149,19 @@ const ALWAYS_IGNORED_URLS__EXPECTED = [ // the same status code. Sometimes it's 404, sometimes 503, sometimes 200 etc. // They do work whenever I've tested them in a browser. "https://csrc.nist.gov/news/2023/three-draft-fips-for-post-quantum-cryptography", + + // Markdown reference definitions with escaped characters that are extracted as links + // Note: Some URLs are extracted with /_ instead of \_ depending on the markdown parser + "http://nlopt.readthedocs.io/en/latest/NLopt\\_Algorithms/", + "http://nlopt.readthedocs.io/en/latest/NLopt/_Algorithms/", + "https://nlopt.readthedocs.io/en/latest/NLopt\\_Algorithms/#controlled-random-search-crs-with-local-mutation", + "https://qiskit.org/ecosystem/aer/apidocs/aer\\_library.html", + "https://qiskit.org/ecosystem/aer/apidocs/aer/_library.html", + "https://optuna.readthedocs.io/en/stable/tutorial/20/_recipes/009/_ask/_and/_tell.html", + "https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin\\_l\\_bfgs\\_b.html", + "https://github.com/Qiskit/openqasm3\\_parser", "https://csrc.nist.gov/pubs/fips/205/ipd", + "https://csrc.nist.gov/pubs/fips/203/ipd", "https://doi.org/10.6028/jres.104.027", "https://eprint.iacr.org/2012/090", "https://finance.yahoo.com/quote/8801.T", @@ -173,6 +192,12 @@ const ALWAYS_IGNORED_URLS__SHOULD_FIX: string[] = [ "https://auth.quantum.ibm.com/api", "https://quantum-computing.cloud.ibm.com", + // Contains escape characters, but is only the hyperlink text anyway; the actual hyperlink is properly formatted and works + "https://en.wikipedia.org/wiki/Time-evolving\\_block\\_decimation", + + // In an old version of qiskit-ibm-runtime that we won't update + "https://qiskit.github.io/qiskit-serverless/migration/migration_from_qiskit_runtime_programs.html", + "https://qiskit.github.io/qiskit-serverless/migration/migration\\_from\\_qiskit\\_runtime\\_programs.html", // Other links that don't seem to exist any more "https://www.cs.bham.ac.uk/~xin/papers/published_tec_sep00_constraint.pdf", "https://www.globaldataquantum.com/en/quantum-portfolio-optimizer/#form",