@@ -159,8 +159,73 @@ def create_cvxpy_problem(self):
159159 d_max = np .infty * np .ones (A .shape [0 ]) # create a vector to avoid putting redundant max constraint on
160160 # duplicate voxels among structure
161161
162- # Adding max/mean constraints
162+ # Adding constraints
163163 for i in range (len (constraint_def )):
164+
165+ item = constraint_def [i ]
166+ item_type = item .get ('type' )
167+
168+ if item_type in ('dose_volume_V' , 'dose_volume_D' ):
169+
170+ params = item .get ('parameters' , {})
171+ cons = item .get ('constraints' , {})
172+
173+ dvh_method = params .get ('dvh_method' , None )
174+ if dvh_method != 'cvar' :
175+ continue
176+
177+ struct = params ['structure_name' ]
178+ voxel_idx = st .get_opt_voxels_idx (struct )
179+ if len (voxel_idx ) == 0 :
180+ continue
181+
182+ limit = float (params ['dose_gy' ])
183+
184+ volume_perc = float (cons ['limit_volume_perc' ])
185+ if not (0.0 < volume_perc < 100.0 ):
186+ raise ValueError (
187+ f"limit_volume_perc must be in (0,100), got { volume_perc } "
188+ )
189+
190+ alpha = 1.0 - volume_perc / 100.0
191+ dose_1d_list = A [voxel_idx , :] @ x * num_fractions
192+
193+ if cons .get ('constraint_type' ) == 'upper' :
194+
195+ label = f"constraint_{ struct } _{ item_type } _upper_a{ alpha :.4f} "
196+
197+ zeta = cp .Variable (name = f"zeta_{ label } " )
198+ w = cp .Variable (len (voxel_idx ), name = f"w_{ label } " )
199+
200+ self .vars [f"zeta_{ label } " ] = zeta
201+ self .vars [f"w_{ label } " ] = w
202+
203+ constraints += [
204+ zeta + (1.0 / ((1.0 - alpha ) * len (voxel_idx ))) * cp .sum (w ) <= limit ,
205+ w >= dose_1d_list - zeta ,
206+ w >= 0
207+ ]
208+
209+ elif cons .get ('constraint_type' ) == 'lower' :
210+
211+ dose_neg = - dose_1d_list
212+ limit_neg = - limit
213+
214+ label = f"constraint_{ struct } _{ item_type } _lower_a{ alpha :.4f} "
215+
216+ zeta = cp .Variable (name = f"zeta_{ label } " )
217+ w = cp .Variable (len (voxel_idx ), name = f"w_{ label } " )
218+
219+ self .vars [f"zeta_{ label } " ] = zeta
220+ self .vars [f"w_{ label } " ] = w
221+
222+ constraints += [
223+ zeta + (1.0 / ((1.0 - alpha ) * len (voxel_idx ))) * cp .sum (w ) <= limit_neg ,
224+ w >= dose_neg - zeta ,
225+ w >= 0
226+ ]
227+
228+
164229 if constraint_def [i ]['type' ] == 'max_dose' :
165230 limit_key = self .matching_keys (constraint_def [i ]['constraints' ], 'limit' )
166231 if limit_key :
@@ -188,48 +253,6 @@ def create_cvxpy_problem(self):
188253 (cp .sum ((cp .multiply (st .get_opt_voxels_volume_cc (org ),
189254 A [st .get_opt_voxels_idx (org ), :] @ x ))))
190255 <= limit / num_fractions ]
191-
192- elif constraint_def [i ]['type' ] == 'CVaR_Upper' :
193-
194- '''
195- Json example:
196- {
197- "type": "CVaR_Upper",
198- "parameters": {
199- "structure_name": "HEART",
200- "alpha": 0.90
201- },
202- "constraints": {
203- "limit": 5
204- }
205- }
206- '''
207-
208- alpha = float (constraint_def [i ]['parameters' ]['alpha' ])
209- if not (0.0 < alpha < 1.0 ):
210- raise ValueError (f"CVaR_Upper: alpha must be in (0,1), got { alpha } " )
211- struct = constraint_def [i ]['parameters' ]['structure_name' ]
212- limit = constraint_def [i ]['constraints' ]['limit' ]
213- if struct in self .my_plan .structures .get_structures ():
214- voxel_idx = st .get_opt_voxels_idx (struct )
215- if len (voxel_idx ) > 0 :
216- dose_1d_list = A [voxel_idx , :] @ x * num_fractions # dose per voxel
217-
218- label = f"constraint_{ struct } _a{ alpha :.4f} "
219-
220- zeta = cp .Variable (name = f"zeta_{ label } " )
221- w = cp .Variable (len (voxel_idx ), name = f"w_{ label } " )
222-
223- # Store variables in self.vars using label
224- self .vars [f"zeta_{ label } " ] = zeta
225- self .vars [f"w_{ label } " ] = w
226-
227- # Add CVaR constraint
228- constraints += [
229- zeta + (1 / ((1 - alpha ) * len (voxel_idx ))) * cp .sum (w ) <= limit ,
230- w >= dose_1d_list - zeta ,
231- w >= 0
232- ]
233256
234257 mask = np .isfinite (d_max )
235258 # Create index mask arrays
@@ -238,38 +261,6 @@ def create_cvxpy_problem(self):
238261 constraints += [A [all_d_max_vox_ind , :] @ x <= d_max [all_d_max_vox_ind ]] # Add constraint for all d_max voxels at once
239262 print ('Problem created' )
240263
241- def add_CVaR_Upper (self , alpha : float , limit : float , struct : str ):
242- """
243- add CVaR+ to the problem as a constraint
244-
245- """
246- constraints = self .constraints
247- x = self .vars ["x" ]
248- st = self .inf_matrix
249- A = self .inf_matrix .A
250- num_fractions = self .clinical_criteria .get_num_of_fractions ()
251-
252- if struct in self .my_plan .structures .get_structures ():
253- voxel_idx = st .get_opt_voxels_idx (struct )
254-
255- if len (voxel_idx ) > 0 :
256- dose_1d_list = A [voxel_idx , :] @ x * num_fractions # dose per voxel
257-
258- label = f"constraint_{ struct } _a{ alpha :.4f} "
259-
260- zeta = cp .Variable (name = f"zeta_{ label } " )
261- w = cp .Variable (len (voxel_idx ), name = f"w_{ label } " )
262-
263- # Store variables in self.vars using label
264- self .vars [f"zeta_{ label } " ] = zeta
265- self .vars [f"w_{ label } " ] = w
266-
267- # Add CVaR constraint
268- constraints += [
269- zeta + (1 / ((1 - alpha ) * len (voxel_idx ))) * cp .sum (w ) <= limit ,
270- w >= dose_1d_list - zeta ,
271- w >= 0
272- ]
273264
274265 def add_max (self , struct : str , dose_gy : float ):
275266 """
0 commit comments