Skip to content

Commit 8bc3c86

Browse files
Transurgeonclaude
andcommitted
Adopt three-way atom classification: smooth, nonsmooth-convex, nonsmooth-concave
Replace the two-axis is_atom_linearizable_convex/concave overrides across all atoms with a single method from the paper's three categories: is_atom_smooth, is_atom_nonsmooth_convex, or is_atom_nonsmooth_concave. The base class derives the old linearizable methods for backward compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 604d8c9 commit 8bc3c86

29 files changed

Lines changed: 97 additions & 250 deletions

CLAUDE.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,23 @@ The diff engine supports CVXPY `Parameter` objects: `C_problem` registers parame
176176
1. Create a canonicalizer in `cvxpy/reductions/dnlp2smooth/canonicalizers/`
177177
2. The canonicalizer converts non-smooth atoms to smooth equivalents using auxiliary variables
178178
3. Register in `canonicalizers/__init__.py` by adding to `SMOOTH_CANON_METHODS` dict
179-
4. Ensure the atom has proper `is_smooth()`, `is_linearizable_convex()`, `is_linearizable_concave()` methods
179+
4. Classify the atom using the three-way atom-level API (see below)
180180

181-
### DNLP Rules (Linearizable Convex / Linearizable Concave)
181+
### DNLP Atom Classification (Three-way)
182+
183+
Each atom implements exactly one of three atom-level methods:
184+
185+
| Category | Method | Examples |
186+
|---|---|---|
187+
| **Smooth** | `is_atom_smooth() → True` | exp, log, power, sin, prod, quad_form |
188+
| **NS-convex** | `is_atom_nonsmooth_convex() → True` | abs, max, norm1, norm_inf, huber |
189+
| **NS-concave** | `is_atom_nonsmooth_concave() → True` | min, minimum |
190+
191+
The base class in `atom.py` derives backward-compatible convenience methods:
192+
- `is_atom_linearizable_convex()` = `is_atom_smooth() or is_atom_nonsmooth_convex()`
193+
- `is_atom_linearizable_concave()` = `is_atom_smooth() or is_atom_nonsmooth_concave()`
194+
195+
### DNLP Expression-level Rules
182196

183197
- **Smooth**: functions that are both linearizable convex and linearizable concave (analogous to affine in DCP)
184198
- **Linearizable Convex**: can be minimized or appear in `<= 0` constraints

cvxpy/atoms/affine/affine_atom.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,8 @@ def is_atom_concave(self) -> bool:
5757
"""
5858
return True
5959

60-
def is_atom_linearizable_convex(self) -> bool:
61-
"""Is the atom convex after linearizing?
62-
"""
63-
return True
64-
65-
def is_atom_linearizable_concave(self) -> bool:
66-
"""Is the atom concave after linearizing?
67-
"""
60+
def is_atom_smooth(self) -> bool:
61+
"""Is the atom smooth?"""
6862
return True
6963

7064
def is_incr(self, idx) -> bool:

cvxpy/atoms/atom.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,25 @@ def is_atom_affine(self) -> bool:
188188
"""
189189
return self.is_atom_concave() and self.is_atom_convex()
190190

191+
def is_atom_smooth(self) -> bool:
192+
"""Is the atom smooth (infinitely differentiable)?"""
193+
return False
194+
195+
def is_atom_nonsmooth_convex(self) -> bool:
196+
"""Is the atom nonsmooth and convex?"""
197+
return False
198+
199+
def is_atom_nonsmooth_concave(self) -> bool:
200+
"""Is the atom nonsmooth and concave?"""
201+
return False
202+
191203
def is_atom_linearizable_convex(self) -> bool:
192-
"""Is the atom convex after linearizing?
193-
"""
194-
raise NotImplementedError("is_atom_linearizable_convex not implemented for %s."
195-
% self.__class__.__name__)
196-
204+
"""Is the atom convex after linearizing?"""
205+
return self.is_atom_smooth() or self.is_atom_nonsmooth_convex()
206+
197207
def is_atom_linearizable_concave(self) -> bool:
198-
"""Is the atom concave after linearizing?
199-
"""
200-
raise NotImplementedError("is_atom_linearizable_concave not implemented for %s."
201-
% self.__class__.__name__)
208+
"""Is the atom concave after linearizing?"""
209+
return self.is_atom_smooth() or self.is_atom_nonsmooth_concave()
202210

203211
def is_atom_log_log_convex(self) -> bool:
204212
"""Is the atom log-log convex?

cvxpy/atoms/elementwise/abs.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,10 @@ def is_atom_concave(self) -> bool:
5656
"""
5757
return False
5858

59-
def is_atom_linearizable_convex(self) -> bool:
60-
"""Is the atom convex after linearizing?
61-
"""
59+
def is_atom_nonsmooth_convex(self) -> bool:
60+
"""Is the atom nonsmooth and convex?"""
6261
return True
6362

64-
def is_atom_linearizable_concave(self) -> bool:
65-
"""Is the atom concave after linearizing?
66-
"""
67-
return False
68-
6963
def is_incr(self, idx) -> bool:
7064
"""Is the composition non-decreasing in argument idx?
7165
"""

cvxpy/atoms/elementwise/entr.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,8 @@ def is_atom_concave(self) -> bool:
5858
"""
5959
return True
6060

61-
def is_atom_linearizable_convex(self) -> bool:
62-
"""Is the atom convex after linearizing?
63-
"""
64-
return True
65-
66-
def is_atom_linearizable_concave(self) -> bool:
67-
"""Is the atom concave after linearizing?
68-
"""
61+
def is_atom_smooth(self) -> bool:
62+
"""Is the atom smooth?"""
6963
return True
7064

7165
def is_incr(self, idx) -> bool:

cvxpy/atoms/elementwise/exp.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,8 @@ def is_atom_concave(self) -> bool:
5555
"""
5656
return False
5757

58-
def is_atom_linearizable_convex(self) -> bool:
59-
"""Is the atom convex after linearizing?
60-
"""
61-
return True
62-
63-
def is_atom_linearizable_concave(self) -> bool:
64-
"""Is the atom concave after linearizing?
65-
"""
58+
def is_atom_smooth(self) -> bool:
59+
"""Is the atom smooth?"""
6660
return True
6761

6862
def is_atom_log_log_convex(self) -> bool:

cvxpy/atoms/elementwise/huber.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,10 @@ def is_atom_concave(self) -> bool:
7171
"""Is the atom concave?"""
7272
return False
7373

74-
def is_atom_linearizable_convex(self) -> bool:
75-
"""Is the atom convex after linearizing?
76-
"""
74+
def is_atom_nonsmooth_convex(self) -> bool:
75+
"""Is the atom nonsmooth and convex?"""
7776
return True
7877

79-
def is_atom_linearizable_concave(self) -> bool:
80-
"""Is the atom concave after linearizing?
81-
"""
82-
return False
83-
8478
def is_incr(self, idx) -> bool:
8579
"""Is the composition non-decreasing in argument idx?"""
8680
return self.args[idx].is_nonneg()

cvxpy/atoms/elementwise/hyperbolic.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,8 @@ def is_atom_concave(self) -> bool:
5050
"""
5151
return False
5252

53-
def is_atom_linearizable_convex(self) -> bool:
54-
"""Is the atom convex after linearizing?
55-
"""
56-
return True
57-
58-
def is_atom_linearizable_concave(self) -> bool:
59-
"""Is the atom concave after linearizing?
60-
"""
53+
def is_atom_smooth(self) -> bool:
54+
"""Is the atom smooth?"""
6155
return True
6256

6357
def is_incr(self, idx) -> bool:
@@ -74,7 +68,7 @@ def _domain(self) -> List[Constraint]:
7468
"""Returns constraints describing the domain of the node.
7569
"""
7670
return []
77-
71+
7872
def _grad(self, values) -> List[Constraint]:
7973
raise NotImplementedError("Gradient not implemented for sinh.")
8074

@@ -107,15 +101,9 @@ def is_atom_concave(self) -> bool:
107101
"""Is the atom concave?
108102
"""
109103
return False
110-
111-
def is_atom_linearizable_convex(self) -> bool:
112-
"""Is the atom convex after linearizing?
113-
"""
114-
return True
115104

116-
def is_atom_linearizable_concave(self) -> bool:
117-
"""Is the atom concave after linearizing?
118-
"""
105+
def is_atom_smooth(self) -> bool:
106+
"""Is the atom smooth?"""
119107
return True
120108

121109
def is_incr(self, idx) -> bool:
@@ -160,10 +148,7 @@ def is_atom_convex(self) -> bool:
160148
def is_atom_concave(self) -> bool:
161149
return False
162150

163-
def is_atom_linearizable_convex(self) -> bool:
164-
return True
165-
166-
def is_atom_linearizable_concave(self) -> bool:
151+
def is_atom_smooth(self) -> bool:
167152
return True
168153

169154
def is_incr(self, idx) -> bool:
@@ -202,10 +187,7 @@ def is_atom_convex(self) -> bool:
202187
def is_atom_concave(self) -> bool:
203188
return False
204189

205-
def is_atom_linearizable_convex(self) -> bool:
206-
return True
207-
208-
def is_atom_linearizable_concave(self) -> bool:
190+
def is_atom_smooth(self) -> bool:
209191
return True
210192

211193
def is_incr(self, idx) -> bool:

cvxpy/atoms/elementwise/kl_div.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,8 @@ def is_atom_concave(self) -> bool:
5555
"""
5656
return False
5757

58-
def is_atom_linearizable_convex(self) -> bool:
59-
"""Is the atom convex after linearizing?
60-
"""
61-
return True
62-
63-
def is_atom_linearizable_concave(self) -> bool:
64-
"""Is the atom concave after linearizing?
65-
"""
58+
def is_atom_smooth(self) -> bool:
59+
"""Is the atom smooth?"""
6660
return True
6761

6862
def is_incr(self, idx) -> bool:

cvxpy/atoms/elementwise/log.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,8 @@ def is_atom_concave(self) -> bool:
5656
"""
5757
return True
5858

59-
def is_atom_linearizable_convex(self) -> bool:
60-
"""Is the atom convex after linearizing?
61-
"""
62-
return True
63-
64-
def is_atom_linearizable_concave(self) -> bool:
65-
"""Is the atom concave after linearizing?
66-
"""
59+
def is_atom_smooth(self) -> bool:
60+
"""Is the atom smooth?"""
6761
return True
6862

6963
def is_atom_log_log_convex(self) -> bool:

0 commit comments

Comments
 (0)