1
1
import jpype
2
2
3
+ from ..api import VariableListener
3
4
from ..constraint import ConstraintFactory
4
5
from .._timefold_java_interop import ensure_init , _generate_constraint_provider_class , register_java_class
5
6
from jpyinterpreter import JavaAnnotation
11
12
from ai .timefold .solver .core .api .score .stream import Constraint as _Constraint
12
13
from ai .timefold .solver .core .api .score import Score as _Score
13
14
from ai .timefold .solver .core .api .score .calculator import IncrementalScoreCalculator as _IncrementalScoreCalculator
14
- from ai .timefold .solver .core .api .domain .variable import PlanningVariableGraphType as _PlanningVariableGraphType , \
15
- VariableListener as _VariableListener
15
+ from ai .timefold .solver .core .api .domain .variable import PlanningVariableGraphType as _PlanningVariableGraphType
16
16
17
17
18
18
Solution_ = TypeVar ('Solution_' )
@@ -35,7 +35,7 @@ class PlanningVariable(JavaAnnotation):
35
35
def __init__ (self , * ,
36
36
value_range_provider_refs : List [str ] = None ,
37
37
allows_unassigned : bool = False ,
38
- graph_type : '_PlanningVariableGraphType' = None ):
38
+ graph_type = None ):
39
39
ensure_init ()
40
40
from ai .timefold .solver .core .api .domain .variable import PlanningVariable as JavaPlanningVariable
41
41
super ().__init__ (JavaPlanningVariable ,
@@ -75,19 +75,37 @@ def __init__(self, *,
75
75
76
76
class ShadowVariable (JavaAnnotation ):
77
77
def __init__ (self , * ,
78
- variable_listener_class : Type ['_VariableListener' ] = None ,
78
+ variable_listener_class : Type [VariableListener ] = None ,
79
79
source_variable_name : str ,
80
80
source_entity_class : Type = None ):
81
81
ensure_init ()
82
82
from .._timefold_java_interop import get_class
83
+ from jpyinterpreter import get_java_type_for_python_type
83
84
from ai .timefold .jpyinterpreter import PythonClassTranslator
84
85
from ai .timefold .solver .core .api .domain .variable import (
85
- ShadowVariable as JavaShadowVariable )
86
+ ShadowVariable as JavaShadowVariable , VariableListener as JavaVariableListener )
87
+
86
88
super ().__init__ (JavaShadowVariable ,
87
89
{
88
90
'variableListenerClass' : get_class (variable_listener_class ),
89
91
'sourceVariableName' : PythonClassTranslator .getJavaFieldName (source_variable_name ),
90
- 'sourceEntityClass' : source_entity_class ,
92
+ 'sourceEntityClass' : get_class (source_entity_class ),
93
+ })
94
+
95
+
96
+ class PiggybackShadowVariable (JavaAnnotation ):
97
+ def __init__ (self , * ,
98
+ shadow_variable_name : str ,
99
+ shadow_entity_class : Type = None ):
100
+ ensure_init ()
101
+ from .._timefold_java_interop import get_class
102
+ from ai .timefold .jpyinterpreter import PythonClassTranslator
103
+ from ai .timefold .solver .core .api .domain .variable import (
104
+ PiggybackShadowVariable as JavaPiggybackShadowVariable )
105
+ super ().__init__ (JavaPiggybackShadowVariable ,
106
+ {
107
+ 'shadowVariableName' : PythonClassTranslator .getJavaFieldName (shadow_variable_name ),
108
+ 'shadowEntityClass' : get_class (shadow_entity_class ),
91
109
})
92
110
93
111
@@ -455,100 +473,6 @@ def resetWorkingSolution(self, workingSolution: Solution_, constraintMatchEnable
455
473
return register_java_class (incremental_score_calculator , java_class )
456
474
457
475
458
- def variable_listener (variable_listener_class : Type ['_VariableListener' ] = None , / , * ,
459
- require_unique_entity_events : bool = False ) -> Type ['_VariableListener' ]:
460
- """Changes shadow variables when a genuine planning variable changes.
461
- Important: it must only change the shadow variable(s) for which it's configured!
462
- It should never change a genuine variable or a problem fact.
463
- It can change its shadow variable(s) on multiple entity instances
464
- (for example: an arrival_time change affects all trailing entities too).
465
-
466
- It is recommended that implementations be kept stateless.
467
- If state must be implemented, implementations may need to override the default methods
468
- resetWorkingSolution(score_director: ScoreDirector) and close().
469
-
470
- The following methods must exist:
471
-
472
- def beforeEntityAdded(score_director: ScoreDirector[Solution_], entity: Entity_);
473
-
474
- def afterEntityAdded(score_director: ScoreDirector[Solution_], entity: Entity_);
475
-
476
- def beforeEntityRemoved(score_director: ScoreDirector[Solution_], entity: Entity_);
477
-
478
- def afterEntityRemoved(score_director: ScoreDirector[Solution_], entity: Entity_);
479
-
480
- def beforeVariableChanged(score_director: ScoreDirector[Solution_], entity: Entity_);
481
-
482
- def afterVariableChanged(score_director: ScoreDirector[Solution_], entity: Entity_);
483
-
484
- If the implementation is stateful, then the following methods should also be defined:
485
-
486
- def resetWorkingSolution(score_director: ScoreDirector)
487
-
488
- def close()
489
-
490
- :param require_unique_entity_events: Set to True to guarantee that each of the before/after methods will only be
491
- called once per entity instance per operation type (add, change or remove).
492
- When set to True, this has a slight performance loss.
493
- When set to False, it's often easier to make the listener implementation
494
- correct and fast.
495
- Defaults to False
496
-
497
- :type variable_listener_class: '_VariableListener'
498
- :type require_unique_entity_events: bool
499
- :rtype: Type
500
- """
501
- ensure_init ()
502
-
503
- def variable_listener_wrapper (the_variable_listener_class ):
504
- from jpyinterpreter import translate_python_class_to_java_class , generate_proxy_class_for_translated_class
505
- from ai .timefold .solver .core .api .domain .variable import VariableListener
506
- methods = ['beforeEntityAdded' ,
507
- 'afterEntityAdded' ,
508
- 'beforeVariableChanged' ,
509
- 'afterVariableChanged' ,
510
- 'beforeEntityRemoved' ,
511
- 'afterEntityRemoved' ]
512
-
513
- missing_method_list = []
514
- for method in methods :
515
- if not callable (getattr (the_variable_listener_class , method , None )):
516
- missing_method_list .append (method )
517
- if len (missing_method_list ) != 0 :
518
- raise ValueError (f'The following required methods are missing from @variable_listener class '
519
- f'{ the_variable_listener_class } : { missing_method_list } ' )
520
-
521
- method_on_class = getattr (the_variable_listener_class , 'requiresUniqueEntityEvents' , None )
522
- if method_on_class is None :
523
- def class_requires_unique_entity_events (self ):
524
- return require_unique_entity_events
525
-
526
- setattr (the_variable_listener_class , 'requiresUniqueEntityEvents' , class_requires_unique_entity_events )
527
-
528
- method_on_class = getattr (the_variable_listener_class , 'close' , None )
529
- if method_on_class is None :
530
- def close (self ):
531
- pass
532
-
533
- setattr (the_variable_listener_class , 'close' , close )
534
-
535
- method_on_class = getattr (the_variable_listener_class , 'resetWorkingSolution' , None )
536
- if method_on_class is None :
537
- def reset_working_solution (self , score_director ):
538
- pass
539
-
540
- setattr (the_variable_listener_class , 'resetWorkingSolution' , reset_working_solution )
541
-
542
- translated_class = translate_python_class_to_java_class (the_variable_listener_class )
543
- java_class = generate_proxy_class_for_translated_class (VariableListener , translated_class )
544
- return register_java_class (the_variable_listener_class , java_class )
545
-
546
- if variable_listener_class : # Called as @variable_listener
547
- return variable_listener_wrapper (variable_listener_class )
548
- else : # Called as @variable_listener(require_unique_entity_events=True)
549
- return variable_listener_wrapper
550
-
551
-
552
476
def problem_change (problem_change_class : Type ['_ProblemChange' ]) -> \
553
477
Type ['_ProblemChange' ]:
554
478
"""A ProblemChange represents a change in 1 or more planning entities or problem facts of a PlanningSolution.
@@ -599,6 +523,7 @@ def wrapper_doChange(self, solution, problem_change_director):
599
523
600
524
__all__ = ['PlanningId' , 'PlanningScore' , 'PlanningPin' , 'PlanningVariable' ,
601
525
'PlanningListVariable' , 'PlanningVariableReference' , 'ShadowVariable' ,
526
+ 'PiggybackShadowVariable' ,
602
527
'IndexShadowVariable' , 'AnchorShadowVariable' , 'InverseRelationShadowVariable' ,
603
528
'ProblemFactProperty' , 'ProblemFactCollectionProperty' ,
604
529
'PlanningEntityProperty' , 'PlanningEntityCollectionProperty' ,
@@ -607,4 +532,4 @@ def wrapper_doChange(self, solution, problem_change_director):
607
532
'planning_entity' , 'planning_solution' , 'constraint_configuration' ,
608
533
'nearby_distance_meter' ,
609
534
'constraint_provider' , 'easy_score_calculator' , 'incremental_score_calculator' ,
610
- 'variable_listener' , ' problem_change' ]
535
+ 'problem_change' ]
0 commit comments