Skip to content

Commit 509932c

Browse files
committed
Initial commit of readme and basic files
0 parents  commit 509932c

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

readme.md

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# EN.601.727 Machine Programming - Assignment 1
2+
3+
🎉 Welcome to your very first assignment in Machine Programming!
4+
5+
In this journey, you’ll get your hands dirty with inductive program synthesis, starting with a bottom-up synthesizer, and ending with a taste of LLM-powered synthesis.
6+
Think of it as teaching a machine how to invent programs from scratch, and then inviting an AI assistant to join the fun.
7+
8+
### ✨ Structure
9+
10+
This assignment has three interconnected parts that gradually build on one another:
11+
12+
- **Shapes DSL (Warm-up with Geometry)**
13+
Explore a small domain-specific language (DSL) for geometric shapes. You’ll implement a bottom-up synthesizer that automatically generates shape expressions based on positive and negative coordinates.
14+
- **Strings DSL (From Shapes to Strings)**
15+
Design a DSL for string manipulation—your own mini “string toolkit.” Then, reuse (and slightly adapt) your synthesizer from Part 1 to automatically generate string-processing programs.
16+
- **LLM-Assisted Synthesis (Humans + Machines)**
17+
Put a large language model (LLM) to work! Using the DSL you designed in Part 2, craft prompts that guide the LLM to synthesize string manipulation programs. Then, analyze what it gets right—and where it stumbles.
18+
19+
### 📦 Deliverables and Submission
20+
21+
You will implement several key functions for each part:
22+
23+
- **Part 1: Bottom-up Synthesis for Shapes**
24+
- `shape_synthesizer.py`: `grow()`
25+
- `enumerative_synthesis.py`: `eliminate_equivalents()`
26+
- `enumerative_synthesis.py`: `synthesize()`
27+
- **Part 2: Bottom-up Synthesis for Strings**
28+
- `strings.py`: (add your new string operations)
29+
- `string_synthesizer.py`: `grow()`
30+
- **Part 3: LLM Synthesis for Strings**
31+
- `llm_string_synthesizer.py`: `generate_prompt()`
32+
- `llm_string_synthesizer.py`: `extract_program()`
33+
34+
### 📌 Grading criteria:
35+
36+
- **Parts 1 & 2**: Autograded. Full credit if you pass all tests within 30 minutes of runtime.
37+
(Hint: the reference solution runs most tasks in <1s, hardest ones in <10s.)
38+
- **Part 3**: Graded manually. Your LLM must solve at least 60% of test cases.
39+
Upload your llm_synthesis_report.json with all prompts/responses—it’s your proof of work.
40+
41+
For Gradescope submission, zip the following 6 (or 7) files:
42+
- `strings.py`
43+
- `enumerative_synthesis.py`
44+
- `shape_synthesizer.py`
45+
- `string_synthesizer.py`
46+
- `llm_string_synthesizer.py`
47+
- `llm_synthesis_report.json`
48+
- (Optional) `readme.md` — for notes, acknowledgements, and AI/collaboration credits.
49+
50+
### 🤝 Collaboration Policy
51+
52+
You are encouraged to discuss ideas with peers.
53+
Do not copy code directly.
54+
Implement your own solution.
55+
If you collaborate (e.g., pair programming, brainstorming), credit your collaborators clearly in your `readme.md`.
56+
57+
### 🤖 Using AI in This Assignment
58+
59+
This is a Machine Programming course—of course you can use LLMs!
60+
LLMs can be great debugging partners, but they won’t give you a working solution right away.
61+
Prompt iteratively, and show that you understand the synthesis algorithms.
62+
Save interesting prompts + responses, and include them in your `readme.md`.
63+
Be explicit about which model you used.
64+
65+
### 🔑 LLM API Key for Part 3
66+
67+
We’ll provide each of you with a Google Gemini API key for Part 3.
68+
The key is for this course only.
69+
Please do not share it, especially outside of the class.
70+
Typical usage for this assignment should not exceed $10.
71+
Excessive usage will be monitored, and we may revoke keys if abused.
72+
73+
### 🧭 Integrity Guidelines
74+
75+
- **Parts 1 & 2**: It’s fine to add smart heuristics in your DSL or synthesizer, but don’t hardcode answers to test cases—that defeats the purpose.
76+
- **Part 3**: Don’t fake the LLM’s output in your `.json` report. Both successes and failures are valuable learning outcomes in this course.
77+
78+
### 📚 Reference
79+
80+
The design of the synthesizer and the Shape DSL is adapted (with permission) from PSET1 in MIT’s Introduction to Program Synthesis, taught by Prof. Armando Solar-Lezama.
81+
82+
83+
Here’s a polished, engaging version of your **Setting up** section in markdown—clearer, a bit more fun, and student-friendly:
84+
85+
# 🚀 Setting Up
86+
87+
First things first—let’s get your environment ready.
88+
89+
1. **Clone the repository**
90+
91+
```bash
92+
git clone https://github.com/machine-programming/assignment-1
93+
```
94+
95+
2. **Move into the assignment directory and install dependencies**
96+
97+
```bash
98+
cd assignment-1
99+
pip install -r requirements.txt
100+
```
101+
102+
3. **Test your setup (don’t panic if it fails!)**
103+
Run the tests for Part 1:
104+
105+
```bash
106+
pytest tests/test_shapes.py
107+
```
108+
109+
You should see the tests run but **all of them fail**.
110+
✅ That’s exactly what we expect—your job in Part 1 is to turn those failures into passes!
111+
112+
113+
# 🎨 Part 1: Bottom-up Synthesis for Shapes
114+
115+
![ring-synthesized](images/ring_synthesized.png)
116+
117+
In this part, we’ll explore a Domain-Specific Language (DSL) for shapes.
118+
This DSL gives you a palette of basic shapes (rectangle, triangle, circle) and shape operations (union, intersection, mirror, subtraction).
119+
120+
At its core, a shape $f$ is just a boolean function:
121+
122+
$$f(x, y) \mapsto \texttt{true}~|~\texttt{false}$$
123+
124+
- `true` means that point $(x, y)$ falls within the shape $f$,
125+
- `false` means that the point $(x, y)$ falls outside of the shape $f$.
126+
127+
### 🎯 Goal of synthesis
128+
129+
Given a set of points with positive/negative labels (`List[Tuple[float, float, bool]]`), synthesize a shape program such that:
130+
- All positive points fall inside the shape
131+
- All negative points stay outside
132+
133+
The image above shows an example with 12 positive and 12 negative points.
134+
The expected synthesized program was `Subtraction(Circle(5,5,4), Circle(5,5,2))`, which produces a ring.
135+
136+
### 🧩 Understanding the DSL
137+
138+
Shapes are implemented in shapes.py.
139+
The base class is `Shape`, which all concrete shapes inherit from.
140+
Each shape must implement the method `interpret(xs, ys)`, which takes two numpy arrays (`x` and `y` coordinates) and returns a boolean array.
141+
142+
Example: the `Circle` class inherits from `Shape`:
143+
144+
``` python
145+
class Circle(Shape):
146+
def interpret(self, xs: np.ndarray, ys: np.ndarray) -> np.ndarray:
147+
return ((xs - self.center.x)**2 + (ys - self.center.y)**2) <= self.radius**2
148+
```
149+
150+
The `interpret` function computes whether each coordinate lies inside the circle (using vectorized numpy operations for speed).
151+
152+
### 📜 Formal DSL Syntax
153+
154+
```
155+
Shape ::= Circle(center: Coordinate, radius: int)
156+
| Rectangle(bottom_left: Coordinate, top_right: Coordinate)
157+
| Triangle(bottom_left: Coordinate, top_right: Coordinate) # right triangle only
158+
| Mirror(Shape) # across line y=x
159+
| Union(Shape, Shape)
160+
| Intersection(Shape, Shape)
161+
| Subtraction(Shape, Shape)
162+
```
163+
164+
- **Terminals**: `Circle`, `Rectangle`, `Triangle` (with fixed parameters)
165+
- **Operators**: `Mirror`, `Union`, `Intersection`, `Subtraction`
166+
167+
### 🔨 Part 1(a). Growing Shapes
168+
169+
Time to roll up your sleeves! Head to `shape_synthesizer.py` and open the `ShapeSynthesizer` class.
170+
This synthesizer inherits from `BottomUpSynthesizer` but specializes in shapes.
171+
Your task is to implement `grow()`, which:
172+
173+
- Takes a current set of shape programs.
174+
- Applies shape operators (union, subtraction, etc.) to generate new programs one level deeper.
175+
- Returns a set that includes both the original programs and the newly grown ones.
176+
177+
Once implemented, your `grow()` function will be the engine that drives bottom-up search over the DSL, which step by step builds increasingly complex shapes.
178+
179+
``` python
180+
def grow(
181+
self,
182+
program_list: List[Shape],
183+
examples: List[Tuple[float, float, bool]]
184+
) -> List[Shape]:
185+
```
186+
187+
> 💡 **Hints & Tips**
188+
> - Symmetry/commutativity:
189+
> Some operations (e.g., `Union(A, B) = Union(B, A)`) generate duplicate programs if you’re not careful. Add checks to prune equivalent programs.
190+
> - Progress tracking:
191+
> When you start generating large numbers of programs, visualization helps. Use `tqdm` to show a progress bar and keep your sanity.
192+
193+
### 🔨 Part 1(b). Eliminating (Observationally) Equivalent Shapes
194+
195+
Now that you can **grow** shapes, the next challenge is to keep your search space from exploding.
196+
For this, we’ll turn to the more general `BottomUpSynthesizer` (in `enumerative_synthesis.py`) and implement a pruning step: **eliminating observationally equivalent programs**.
197+
198+
Two programs are **observationally equivalent** if they produce the **same outputs** on the **same inputs**. For example, look at these two programs:
199+
* `Union(Circle(center=(0,0), r=1), Circle(center=(0,0), r=1))`
200+
* `Circle(center=(0,0), r=1)`
201+
These two programs are *different syntactically* but *indistinguishable observationally* (their outputs match on all test points).
202+
203+
Your job is to filter out duplicates like these so the synthesizer only keeps *unique behaviors*. Please implement the `eliminate_equivalents` function:
204+
205+
```python
206+
def eliminate_equivalents(
207+
self,
208+
program_list: List[T],
209+
test_inputs: List[Any],
210+
cache: Dict[T, Any],
211+
iteration: int
212+
) -> Generator[T, None, Dict[T, Any]]:
213+
```
214+
215+
* **`program_list`**: candidate programs to check
216+
* **`test_inputs`**: inputs on which programs will be interpreted
217+
* **`cache`**: a dictionary (`Dict[T, Any]`) mapping each program → its output signature (so you don’t recompute unnecessarily)
218+
* **`iteration`**: current synthesis round (useful for debugging/logging)
219+
* **Return**: a **generator** that yields only the *observationally unique* programs
220+
221+
> 💡 Hints & Tips
222+
> - Use the provided `compute_signature()` method (already implemented) to evaluate programs and produce signatures. These signatures will be your deduplication keys.
223+
> - Keep track of which signatures you’ve already seen using `Set` or `Dict`.
224+
> Be careful: different programs may map to the *same* signature—yield only the first and discard the rest.
225+
> - **Important**: use `yield` instead of returning a list. This way, the synthesizer can stop early if it finds a successful program before exhausting the search space.
226+
> - The `cache` is your friend: store previously computed outputs there to save time when the same program shows up again.
227+
228+
### 🔨 Part 1(c). Bottom-up Synthesizing Shapes
229+
230+
Now is the time to take all that we have already and iteratively synthesize shapes.
231+
232+
233+
234+
235+
236+
# Part 2: Bottom-up Synthesis for Strings
237+
238+
239+
240+
# Part 3: LLM Synthesis for Strings

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
numpy>=1.21.0
2+
google-generativeai>=0.3.0
3+
tqdm>=4.64.0
4+
matplotlib>=3.5.0

0 commit comments

Comments
 (0)