Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Pyomo Homework 3

Course policies and submitting this assignment

Read the Artificial Intelligence Policy and Collaboration Policy and Honor Code before starting.

Submit two files to Canvas:

  1. One scanned PDF containing all handwritten work for Problem 1 and 3-A. Put the work in problem order and label each part. A clear phone photo assembled into a single PDF is fine.

  2. A copy of this notebook, with your code cells run.

The three AI categories are:

  • No AI.

  • AI permitted after independent work --- spend the stated time on your own first, then AI and genuine collaboration (including coding together) are permitted. Everyone must still contribute intellectually, understand the work, and verify it.

  • AI required --- independent work first, then using an AI tool is part of the problem.

Each problem below states its own category and independent-work window --- see the note at the start of each problem rather than a single rule for the whole assignment.

At the end of each top-level problem, add a concise AI and independent-work report: approximately how long the independent attempt took, how far you got, where you became stuck, any AI or collaborative help used afterward, and how you verified it. If you used no AI, say so. Do not submit prompts or transcripts. Time estimates help the instructor improve the assignment and are not a speed test.

How this assignment is organized

This assignment has three problems:

  1. formulate reactor-selection logic and reformulate it by hand;

  2. compare big-MM and convex-hull formulations for strip packing; and

  3. audit an AI-generated finite-difference model of transient heat conduction.

Problems 1 and 2 continue the thread that began in Problem 3 of Pyomo Homework 2. Problem 3 asks you to make an independent modeling plan before using AI, then verify the result with equations, structural diagnostics, and a mesh-refinement check.

#ProblemFormat
1Reactor-selection GDPpencil and paper, no solver, no code cell
2Strip packing: big-MM vs. convex hullnotebook/code
3-ADerive and plan the heat-conduction modelpencil and paper, no AI
3-B -- 3-EImplement, audit, and verify the modelnotebook/code, AI required after 3-A
# Import the libraries you need here for the assignment

import sys

if "google.colab" in sys.modules:
    !wget "https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/helper.py"
    import helper

    helper.easy_install()
else:
    sys.path.insert(0, "../")
    import helper
helper.set_plotting_style()

import pyomo.environ as pyo
import matplotlib.pyplot as plt

Problem 1. Generalized disjunctive programming (GDP) modeling

AI category: AI permitted after independent work. Spend up to 30 minutes on paper, without AI and without the solution pages, before checking any help against it. Pencil and paper, no solver, no code cell. This problem is written in the format of the in-person midterm.

Background. You are screening a superstructure for a new gas-processing plant. The design has two reactor options and three separator options:

SymbolUnit
R1R_1low-conversion reactor (cheap)
R2R_2high-conversion reactor (expensive)
AAabsorber
MMmembrane
CSCScryogenic separation

Write PuP_u for the proposition “unit uu is installed”, and let yuy_u be the corresponding binary variable.

The process engineers hand you six design rules, in English:

  1. Exactly one reactor is installed.

  2. At least one separator is installed.

  3. Cryogenic separation is only worth its capital cost when the high-conversion reactor is installed.

  4. The absorber and the membrane cannot both be installed --- they compete for the same plot space.

  5. If the high-conversion reactor is installed, then cryogenic separation or the membrane (or both) must be installed --- that reactor’s effluent carries light ends the absorber alone cannot remove.

  6. If cryogenic separation is installed and the absorber is not, then the membrane must be installed.

The reactor choice also fixes the achievable conversion window and the reactor capital cost cRc_R [million USD]:

ReactorConversion windowcRc_R [million USD]
R1R_10.30≤X≤0.600.30 \leq X \leq 0.6040
R2R_20.80≤X≤0.950.80 \leq X \leq 0.9590

Conversion is physically bounded by 0≤X≤10 \leq X \leq 1.

Instruction. Same notation rules as Problem 3 of Pyomo Homework 2: capital YY for Booleans, lower-case yy for binaries, inequalities in the standard form g(x)≤0g(x) \leq 0.

1-A. Translate the design into a mathematical model using set notation similar to our in-class examples

Next to each set, parameter, and variable, write a few-word description.

Sets.

Parameters (Data). Hint: state the units of each one.

Variables. Say for each one whether it is continuous, binary, or Boolean --- and be explicit about the relationship between a Boolean YuY_u and its binary yuy_u.

1-B. Write each design rule as a logic proposition

Write rules 1 through 6 in the symbols PR1,PR2,PA,PM,PCSP_{R_1}, P_{R_2}, P_A, P_M, P_{CS} using ¬\neg, ∧\wedge, ∨\vee, ⊻\veebar and ⇒\Rightarrow. Do not convert to constraints yet.

Watch the parentheses. Rules 5 and 6 are the two where a careless reading gives the wrong proposition.

1-C. Convert the propositions to linear constraints

Convert each of rules 1 through 6 into linear constraints on the binaries yuy_u.

For rules 1, 2, 3 and 4 you may quote the translation table from lecture --- give the row you used.

For rules 5 and 6, show the full three-step derivation:

    ① replace the implication   ② apply De Morgan   ③ distribute ∨\vee over ∧\wedge to reach conjunctive normal form,

then substitute yuy_u for PuP_u and 1−yu1 - y_u for ¬Pu\neg P_u, and write one constraint per clause.

Finally, sanity-check your rule 6 constraint by substituting a case where the antecedent fires and a case where it does not.

1-D. Write the reactor choice as a disjunction

The conversion window and the reactor cost are not logic on binaries --- they switch a block of constraints on a continuous variable in or out. Write the reactor choice in GDP standard form: the disjunction over the reactor options, with the conversion bounds and the cost assignment inside each disjunct, and the appropriate Ω(Y)\Omega(Y).

Then answer: which of your six rules from 1-B is now redundant, because Ω(Y)\Omega(Y) already says it?

1-E. Problem size

Let NrN_r be the number of reactor options and NsN_s the number of separator options. Using these symbols, determine the size of the model after the disjunction has been reformulated with big-MM. After each header below, give a number with a brief justification.

Number of continuous variables:

Number of integer/discrete variables:

Number of equality constraints:

Number of inequality constraints:

Then answer the interesting part: which of these counts actually grow with NrN_r and NsN_s, and which do not? Look carefully at the six logic constraints before you answer.

1-F. Classify the problem

Is the reformulated model an LP, QP, NLP, MILP or MINLP? Justify your classification by referring to the objective, the constraints and the variable domains.

Then: is the problem convex? Be careful --- answer separately for the model itself and for its relaxation, and connect your answer to what you found in Problem 3-D of Pyomo Homework 2.

Problem 1 AI use report

At the end of Problem 1, report for the problem as a whole:

  • approximately how long you spent on the single 30-minute independent attempt for Problem 1;

  • how far you got during that independent attempt;

  • where you got stuck, if anywhere;

  • any AI or collaborative help you used afterward; and

  • how you verified that help.

If you used no AI, say so explicitly. A few concise bullets are sufficient; do not submit prompts or transcripts. The time estimate gives the instructor useful data for improving the assignment and is not a speed test. Apply the 30-minute guideline once to the entire problem, not separately to each subpart.

Problem 2. Strip packing: big-MM versus convex hull in Pyomo

AI category: AI permitted after independent work. Spend up to 30 minutes attempting 2-A on your own before using AI or the class notebook.

Problem 3 of Pyomo Homework 2 and Problem 1 above asked you to reformulate a disjunction by hand, on a problem small enough to see all of. This problem hands both reformulations to Pyomo on a problem that is not small, and asks what each one costs.

Background. Eight rectangles must be packed, without rotation and without overlap, into a strip of fixed width W=10W = 10. Rectangle i∈Ni \in N has length LiL_i (along the strip) and height HiH_i (across it), and is placed by the coordinates (xi,yi)(x_i, y_i) of its lower-left corner. The objective is to minimize the length of strip used, ltlt:

min⁡x,y,lt  lts.t.lt≥xi+Li∀i∈N\min_{x, y, lt} \; lt \qquad \text{s.t.} \qquad lt \geq x_i + L_i \quad \forall i \in N

Non-overlap is a disjunction for every pair of rectangles (i,j)(i,j) with i<ji < j, with four disjuncts --- ii is left of jj, ii is right of jj, ii is below jj, or ii is above jj:

[Yij1xi+Li≤xj]∨[Yij2xj+Lj≤xi]∨[Yij3yi+Hi≤yj]∨[Yij4yj+Hj≤yi]\begin{bmatrix} Y_{ij}^{1} \\ x_{i} + L_{i} \leq x_{j} \end{bmatrix} \vee \begin{bmatrix} Y_{ij}^{2} \\ x_{j} + L_{j} \leq x_{i} \end{bmatrix} \vee \begin{bmatrix} Y_{ij}^{3} \\ y_{i} + H_{i} \leq y_{j} \end{bmatrix} \vee \begin{bmatrix} Y_{ij}^{4} \\ y_{j} + H_{j} \leq y_{i} \end{bmatrix}

Nothing about the method changes from Problem 3 of Pyomo Homework 2 --- there are just (82)=28\binom{8}{2} = 28 disjunctions instead of one, and four terms instead of three.

Reference. Strip packing instance from the MINLP library, https://www.minlp.org/library/problem/index.php?i=121&lib=GDP (Vecchietti and Grossmann). The class notebook Modeling Disjunctions through the Strip Packing Problem works this model in full --- attempt 2-A yourself before opening it.

2-A. Build the GDP model

Complete create_model() below by adding the non-overlap disjunctions. Use the @model.Disjunction decorator (this is the notation you practised in Problem 1-J of Pyomo Homework 2) indexed over model.overlap_pairs, and return the four disjuncts as a Python list of expressions.

Do not write big-MM or convex hull constraints by hand. The whole point of writing the disjunction is that the transformation is somebody else’s job.

from pyomo.environ import (
    check_optimal_termination,
    ConcreteModel,
    Constraint,
    NonNegativeReals,
    Objective,
    Param,
    Set,
    SolverFactory,
    TransformationFactory,
    Var,
    value,
)


def create_model():
    """Build the strip packing problem as a generalized disjunctive program.

    Returns:
        model: Pyomo model, with the no-overlap disjunctions attached but NOT
            yet reformulated into a MILP.
    """
    model = ConcreteModel(name="Rectangles strip packing")

    ## Sets
    model.rectangles = Set(ordered=True, initialize=[0, 1, 2, 3, 4, 5, 6, 7])

    ## Parameters
    # Extent of each rectangle across the width of the strip (the y direction)
    model.rect_width = Param(
        model.rectangles, initialize={0: 3, 1: 3, 2: 2, 3: 2, 4: 3, 5: 5, 6: 7, 7: 7}
    )

    # Extent of each rectangle along the strip (the x direction)
    model.rect_length = Param(
        model.rectangles, initialize={0: 4, 1: 3, 2: 2, 3: 2, 4: 3, 5: 3, 6: 4, 7: 4}
    )

    model.strip_width = Param(initialize=10, doc="Available width of the strip")

    # Upper bound on length: every rectangle laid end to end
    model.max_length = Param(
        initialize=sum(model.rect_length[i] for i in model.rectangles)
    )

    ## Variables
    model.x = Var(
        model.rectangles,
        bounds=(0, model.max_length),
        doc="Rectangle corner position along the strip",
    )

    def w_bounds(b, i):
        return (0, b.strip_width - b.rect_width[i])

    model.y = Var(
        model.rectangles, bounds=w_bounds, doc="Rectangle corner position across the strip"
    )

    model.strip_length = Var(domain=NonNegativeReals, doc="Length of strip required")

    # The 28 unordered pairs of rectangles
    model.overlap_pairs = Set(
        initialize=model.rectangles * model.rectangles,
        dimen=2,
        filter=lambda b, i, j: i < j,
        doc="Set of possible rectangle conflicts",
    )

    ## Constraints
    @model.Constraint(model.rectangles)
    def strip_ends_after_last_rec(b, i):
        return b.strip_length >= b.x[i] + b.rect_length[i]

    ## Objective
    model.total_length = Objective(expr=model.strip_length, doc="Minimize length")

    ## Add the no-overlap disjunctions here!

    # Add your solution here

    return model


print(f"{len(create_model().overlap_pairs)} pairs of rectangles, one disjunction each")

2-B. Measure the size of a model

Before solving anything, write a function that reports the size of a Pyomo model: how many continuous variables, how many binary variables, and how many active constraints it has.

This is the same count you did by hand in Problem 3-E of Pyomo Homework 2, now done by the software. Use model.component_data_objects(Var, active=True) and the same for Constraint, and test each variable with .is_binary() and .is_continuous().

def model_size(model):
    """Count the continuous variables, binary variables, and active constraints.

    Argument:
        model: a Pyomo model, AFTER a GDP transformation has been applied

    Returns:
        dict with keys "continuous", "binary", "constraints"
    """
    # Add your solution here
    return {"continuous": n_cont, "binary": n_bin, "constraints": n_con}

2-C. Solve with the big-MM reformulation

Apply TransformationFactory("gdp.bigm") to a fresh model, solve it with HiGHS, and print the optimal strip length and the placement of every rectangle.

Hint: the solver returns coordinates a few floating-point units off a whole number (6.999999999999998, -0.0). Print with a format like %.4g --- that is numerical noise, not geometry.

def solve_variant(transformation, tee=False):
    """Build, transform, and solve the strip packing model.

    Argument:
        transformation: "gdp.bigm" or "gdp.hull"

    Returns:
        (model, size) -- the solved model and its size dict
    """
    model = create_model()
    # Add your solution here
    size = model_size(model)
    results = SolverFactory("appsi_highs").solve(model, tee=tee)
    assert check_optimal_termination(results), (
        f"Solve failed: status={results.solver.status}, "
        f"termination={results.solver.termination_condition}"
    )
    return model, size


bigm_model, bigm_size = solve_variant("gdp.bigm")

print("big-M reformulation")
print(f"  optimal strip length lt = {value(bigm_model.total_length):.4g}")
for i in bigm_model.rectangles:
    # +0.0 turns the solver's -0.0 into 0.0; .4g hides floating-point noise
    # such as 6.999999999999998, which is arithmetic, not geometry.
    xi = value(bigm_model.x[i]) + 0.0
    yi = value(bigm_model.y[i]) + 0.0
    print(f"  rectangle {i} at ({xi:.4g}, {yi:.4g})")
print(" ", bigm_size)

2-D. Solve with the convex hull reformulation, and compare

Do the same with gdp.hull. Then build the comparison.

Warning: A solve time depends on the machine, the solver version, and what else the computer is doing, so a timing you print here is not a number your classmate can reproduce.

Compare the two on things that are properties of the model:

  1. Size: continuous variables, binary variables, constraints, from model_size.

  2. Tightness of the relaxation: relax every binary to 0≤y≤10 \leq y \leq 1 with TransformationFactory("core.relax_integer_vars") and solve the resulting LP. Its objective is the bound branch and bound starts from at the root node.

Report a table with both, plus the MILP optimum and the 109/10=10.9109/10 = 10.9 area bound from the note above. Then answer, in a few sentences each:

  1. Do the two reformulations reach the same optimal ltlt? Do they reach it by the same placement? (2-E will show you.)

  2. Which relaxation is tighter, and by how much? Compare each root bound to the true optimum and to the area bound.

  3. The hull model is several times larger. Which kind of variable did it add --- and why does that make the trade worth taking? Connect this to your answer in Problem 3-E of Pyomo Homework 2.

def root_relaxation_bound(transformation):
    """Objective of the LP relaxation: the bound branch and bound starts from.

    Fully deterministic and machine-independent, unlike a solve time.
    """
    model = create_model()
    TransformationFactory(transformation).apply_to(model)
    # Add your solution here
    results = SolverFactory("appsi_highs").solve(model)
    assert check_optimal_termination(results)
    return value(model.total_length)


hull_model, hull_size = solve_variant("gdp.hull")

print("convex hull reformulation")
print(f"  optimal strip length lt = {value(hull_model.total_length):.4g}")
for i in hull_model.rectangles:
    # +0.0 turns the solver's -0.0 into 0.0; .4g hides floating-point noise
    # such as 6.999999999999998, which is arithmetic, not geometry.
    xi = value(hull_model.x[i]) + 0.0
    yi = value(hull_model.y[i]) + 0.0
    print(f"  rectangle {i} at ({xi:.4g}, {yi:.4g})")
print(" ", hull_size)

area_bound = sum(
    value(bigm_model.rect_length[i]) * value(bigm_model.rect_width[i])
    for i in bigm_model.rectangles
) / value(bigm_model.strip_width)

rows = [
    ("continuous variables", bigm_size["continuous"], hull_size["continuous"]),
    ("binary variables", bigm_size["binary"], hull_size["binary"]),
    ("constraints", bigm_size["constraints"], hull_size["constraints"]),
    ("LP relaxation bound at the root", root_relaxation_bound("gdp.bigm"),
     root_relaxation_bound("gdp.hull")),
    ("MILP optimum", value(bigm_model.total_length), value(hull_model.total_length)),
]

print(f"\n{'':35s} {'gdp.bigm':>10s} {'gdp.hull':>10s}")
for label, a, b in rows:
    print(f"{label:35s} {a:10.6g} {b:10.6g}")
print(f"\nArea lower bound (no packing can beat this): {area_bound:.4g}")
print("Wall-clock time is deliberately NOT reported: it is machine dependent.")

2-E. Visualize the packing

A table of coordinates is not a packing. Write a function that draws the solved model: one rectangle per item, drawn at (xi,yi)(x_i, y_i) with width LiL_i and height HiH_i, plus a line marking the strip length ltlt. Use it on both solutions, one above the other.

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle


def plot_packing(model, title, ax):
    """Draw a solved strip packing.

    Greyscale safety: every rectangle carries its own index printed in the
    middle AND its own hatch pattern, so no rectangle is identified by colour
    alone. The fills are light tints so that the black label and the black
    hatch stay legible in a photocopy.
    """
    # Light tints of the house Okabe-Ito palette. They are TINTS on purpose:
    # a saturated fill swallows the black index label and the black hatch, and
    # those two are what make the figure work in a photocopy.
    fills = ["#FFFFFF", "#FFE7B3", "#CDE6F5", "#CCEBDD"]
    hatches = ["", "///", "\\\\\\", "...", "xxx", "|||", "---", "+++"]

    W = value(model.strip_width)
    lt = value(model.strip_length)

    # Add your solution here

    ax.axvline(lt, color="black", linestyle="--", linewidth=1.5)
    ax.text(lt + 0.15, W * 0.94, f"$lt$ = {lt:g}", ha="left", va="top")
    ax.axhline(0, color="black", linewidth=1.0)
    ax.axhline(W, color="black", linewidth=1.0)
    ax.set_xlim(-0.4, max(lt, 1) + 3.5)
    ax.set_ylim(-0.4, W + 0.4)
    ax.set_aspect("equal")
    ax.set_xlabel("position along the strip, $x$")
    ax.set_ylabel("across, $y$")
    ax.set_title(title)
    return ax


fig, axes = plt.subplots(2, 1, figsize=(8, 7))
plot_packing(bigm_model, "gdp.bigm", axes[0])
plot_packing(hull_model, "gdp.hull", axes[1])
fig.tight_layout()
plt.show()

2-F. Discussion

Two or three sentences each.

  1. Both reformulations describe the same set of feasible packings, yet 2-E almost certainly shows you two different pictures. Explain how both can be correct.

  2. How do the disjunctions affect the degree of freedom analysis? Count the binaries that gdp.bigm introduced and say where the number comes from.

  3. The number of disjunctions grows as (N2)\binom{N}{2} in the number of rectangles. Contrast this with the logic constraints in 1-E, which did not grow at all. What is the difference between the two situations?

  4. You wrote the model once, as a disjunction, and got two MILPs from it for free. Name one thing that would have gone wrong if you had typed the big-MM constraints by hand instead --- and refer to your answer in Problem 3-B part 3 of Pyomo Homework 2.

Problem 2 AI use report

At the end of Problem 2, report for the problem as a whole:

  • approximately how long you spent on the single 30-minute independent attempt for Problem 2;

  • how far you got during that independent attempt;

  • where you got stuck, if anywhere;

  • any AI or collaborative help you used afterward; and

  • how you verified that help.

If you used no AI, say so explicitly. A few concise bullets are sufficient; do not submit prompts or transcripts. The time estimate gives the instructor useful data for improving the assignment and is not a speed test. Apply the 30-minute guideline once to the entire problem, not separately to each subpart.

Problem 3. Audit an AI-generated heat-conduction model

AI category: AI required after independent work. Spend 20 minutes deriving the model and planning its discretization without AI or solution code (3-A). Record that work on paper. Then ask an AI tool to help implement the model and audit its response (3-B onward).

Consider transient radial conduction in a slab of half-thickness RR:

∂T∂t=α∂2T∂r2,0<r<R,\frac{\partial T}{\partial t}=\alpha\frac{\partial^2T}{\partial r^2}, \qquad 0<r<R,

with T(r,0)=T0T(r,0)=T_0, symmetry at r=0r=0, and a prescribed surface temperature T(R,t)=TsT(R,t)=T_s. Use dimensionless variables so that R=1R=1, α=1\alpha=1, T0=0T_0=0, and Ts=1T_s=1.

You may consult the course heat-conduction notebook and the ND Pyomo Cookbook chapter after recording your independent plan in 3-A. These are verification sources, so inspecting their code is allowed for this problem. This differs from Project 1, where the published implementation is intentionally withheld until after your independent validation.

3-A. Derive and plan without AI

Turn in a concise handwritten response. If any of these terms are unfamiliar, the linked pages are a fine place to start --- this problem is as much about learning to navigate PDE vocabulary you have not seen before as it is about the specific slab.

  1. Write the initial condition and both boundary conditions.

  2. Explain why the center condition is a Neumann condition and the surface condition is a Dirichlet condition.

  3. Sketch a finite-difference stencil for the interior second derivative.

  4. Predict what can go wrong if a central-difference transformation is applied before the center derivative is defined.

3-B. Generate a candidate implementation

Ask an AI tool to write a Pyomo.DAE implementation that:

  • discretizes space with finite differences and time with orthogonal collocation;

  • enforces the initial and boundary conditions without duplicate corner equations;

  • has zero degrees of freedom after discretization; and

  • solves with Ipopt.

Run the code, but do not treat a successful solve as verification. Save only the final code you audit; you do not need to submit a transcript.

3-C. Audit structure before solving

Ask an AI tool to help you read the output of this step --- the diagnostics below are dense, and understanding what they mean is the point, not just running them. Two notebooks work through the same kind of audit in more depth if you want another worked example: the course’s heat-conduction notebook (this exact model, three geometries) and NLP Diagnostics (build_model_size_report and DiagnosticsToolbox on other models).

Use build_model_size_report() and DiagnosticsToolbox to check the generated model. Then answer:

  1. Is the discretized model square after fixed variables are accounted for?

  2. Which constraint defines the center derivative after spatial discretization?

  3. Are the initial and surface conditions both imposed at the corner (r,t)=(1,0)(r,t)=(1,0)? Explain how the implementation avoids a conflict.

  4. Identify and correct at least one modeling or implementation weakness. If the first AI response is correct, perturb one boundary-condition line deliberately and show that your audit catches the defect.

# Add your solution here

3-D. Verify the numerical result

Solve the corrected model on at least two spatial meshes. Report the center temperature at t=0.2t=0.2 and the change caused by mesh refinement. Plot the temperature profile for the finer mesh.

Also compare the qualitative behavior with the course notebook:

  • temperature remains between the initial and surface temperatures;

  • the profile is symmetric about the slab center; and

  • heat propagates inward from the surface.

A visually plausible curve is evidence, but it is not a substitute for the structural audit.

# Add your solution here

3-E. Compare against the analytic solution

3-D verified your model the way you would for a PDE with no known closed-form solution: mesh refinement and qualitative physical checks. This particular slab problem happens to have an exact series solution, so use it to see how good those proxies actually were.

T(r,t)=1−∑n=0∞4(2n+1)π(−1)ncos⁡(λnr) e−λn2t,λn=(2n+1)π2.T(r,t) = 1 - \sum_{n=0}^{\infty} \frac{4}{(2n+1)\pi}(-1)^n \cos(\lambda_n r)\, e^{-\lambda_n^2 t}, \qquad \lambda_n = \frac{(2n+1)\pi}{2}.
  1. Evaluate this series at the center (r=0r=0, t=0.2t=0.2); a few dozen terms is plenty.

  2. Report the absolute error between it and your center temperature from 3-D, for both meshes.

  3. Does the error get smaller as you refine the mesh? Does it shrink smoothly, or level off?

  4. Most PDEs you discretize in practice will not have a closed-form solution. Which of your 3-D checks would you still trust if this series did not exist?

# Add your solution here

Problem 3 AI use report

At the end of Problem 3, report for the problem as a whole:

  • approximately how long you spent on the 20-minute independent attempt in 3-A;

  • the result of that independent attempt, and how far you got;

  • what the AI tool produced or changed from 3-B onward;

  • the checks that established whether the result was trustworthy;

  • at least one defect you found or deliberately introduced in 3-C; and

  • one limitation of your verification.

If you used no AI, say so explicitly. Do not submit prompts or transcripts.