Skip to content

Commit d76c96f

Browse files
authored
docs: add kotlin code for service docs (TimefoldAI#2421)
1 parent 9aee73c commit d76c96f

5 files changed

Lines changed: 532 additions & 2 deletions

File tree

docs/src/modules/ROOT/pages/service/constraint-weights.adoc

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ The implementation should have fields that refer to specific constraints using t
2222
To ensure both the constraint and this reference are the same, use a static field to keep the name of the constraint.
2323

2424
.The example ConstraintProvider class.
25+
[tabs]
26+
====
27+
Java::
28+
+
29+
--
2530
[source,java,options="nowrap"]
2631
----
2732
public class TimetableConstraintProvider implements ConstraintProvider {
@@ -44,8 +49,44 @@ public class TimetableConstraintProvider implements ConstraintProvider {
4449
// other constraints excluded
4550
}
4651
----
52+
--
53+
54+
Kotlin::
55+
+
56+
--
57+
[source,kotlin,options="nowrap"]
58+
----
59+
class TimetableConstraintProvider : ConstraintProvider {
60+
61+
companion object {
62+
const val TEACHER_CONFLICT = "Teacher conflict"
63+
const val ROOM_CONFLICT = "Room conflict"
64+
}
65+
66+
fun roomConflict(constraintFactory: ConstraintFactory): Constraint {
67+
return constraintFactory
68+
// constraint implementation excluded
69+
.asConstraint(ROOM_CONFLICT)
70+
}
71+
72+
fun teacherConflict(constraintFactory: ConstraintFactory): Constraint {
73+
return constraintFactory
74+
// constraint implementation excluded
75+
.asConstraint(TEACHER_CONFLICT)
76+
}
77+
78+
// other constraints excluded
79+
}
80+
----
81+
--
82+
====
4783

4884
.The ModelConfigOverrides class.
85+
[tabs]
86+
====
87+
Java::
88+
+
89+
--
4990
[source,java,options="nowrap"]
5091
----
5192
public final class TimetableConfigOverrides implements ModelConfigOverrides {
@@ -63,6 +104,29 @@ public final class TimetableConfigOverrides implements ModelConfigOverrides {
63104
64105
}
65106
----
107+
--
108+
109+
Kotlin::
110+
+
111+
--
112+
[source,kotlin,options="nowrap"]
113+
----
114+
data class TimetableConfigOverrides(
115+
@ConstraintReference(TimetableConstraintProvider.TEACHER_CONFLICT)
116+
val teacherConflictWeight: Long = DEFAULT_WEIGHT_ONE,
117+
@ConstraintReference(TimetableConstraintProvider.ROOM_CONFLICT)
118+
val roomConflictWeight: Long = DEFAULT_WEIGHT_ONE
119+
) : ModelConfigOverrides {
120+
121+
companion object {
122+
const val DEFAULT_WEIGHT_ZERO = 0L
123+
const val DEFAULT_WEIGHT_ONE = 1L
124+
}
125+
126+
}
127+
----
128+
--
129+
====
66130

67131
The default constraint weight for these constraints is `1`. This can now be overridden by the consumer by passing in the model overrides object in a request.
68132
For example, to make the Teacher conflict 10 times more impactful, override the weight to 10:
@@ -93,6 +157,11 @@ Usually, it doesn't make sense to allow weight overrides for _hard_ constraints.
93157
Next, in the xref:./rest-api.adoc#modelConverter[model converter], make sure to map these overrides to a solver specific `ConstraintWeightOverrides` object that must be on the `@PlanningSolution` class.
94158

95159
.As part of the ModelConvertor
160+
[tabs]
161+
====
162+
Java::
163+
+
164+
--
96165
[source,java,options="nowrap"]
97166
----
98167
TimetableConfigOverrides modelConfigOverrides = modelConfig.overrides();
@@ -108,6 +177,28 @@ ConstraintWeightOverrides<HardMediumSoftLongScore> constraintWeightOverrides = C
108177
109178
solverModel.setConstraintWeightOverrides(constraintWeightOverrides);
110179
----
180+
--
181+
182+
Kotlin::
183+
+
184+
--
185+
[source,kotlin,options="nowrap"]
186+
----
187+
val modelConfigOverrides = modelConfig.overrides()
188+
189+
val constraintWeightOverrides = ConstraintWeightOverrides.of(
190+
mapOf(
191+
TimetableConstraintProvider.TEACHER_CONFLICT to
192+
HardMediumSoftLongScore.ofHard(modelConfigOverrides.teacherConflictWeight),
193+
TimetableConstraintProvider.ROOM_CONFLICT to
194+
HardMediumSoftLongScore.ofSoft(modelConfigOverrides.roomConflictWeight)
195+
)
196+
)
197+
198+
solverModel.constraintWeightOverrides = constraintWeightOverrides
199+
----
200+
--
201+
====
111202

112203
For more information, see xref:../constraints-and-score/constraint-configuration.adoc#constraintConfiguration[Adjusting constraints at runtime].
113204

docs/src/modules/ROOT/pages/service/demo-data.adoc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ This interface requires you to implement 2 methods:
2323
Implementations of this interface must be dependency free meaning simple instantiation (even with reflection) of this class is enough to generate demo data.
2424
====
2525

26+
[tabs]
27+
====
28+
Java::
29+
+
30+
--
2631
[source,java,options="nowrap"]
2732
----
2833
@ApplicationScoped
@@ -79,6 +84,53 @@ public class TimetableDemoDataGenerator implements DemoDataGenerator {
7984
}
8085
}
8186
----
87+
--
88+
89+
Kotlin::
90+
+
91+
--
92+
[source,kotlin,options="nowrap"]
93+
----
94+
@ApplicationScoped
95+
class TimetableDemoDataGenerator : DemoDataGenerator {
96+
97+
enum class DemoDataKind(
98+
private val metaData: DemoMetaData,
99+
private val requestFunction: (DemoDataKind) -> ModelRequest<TimetableInput, TimetableConfigOverrides>
100+
) {
101+
BASIC(
102+
DemoMetaData("BASIC", "SHORT_DESCRIPTION", "LONG_DESCRIPTION", listOf("TAGS"), listOf()),
103+
{ it.generateBasicDemoData() } // could also delegate to another class instead
104+
),
105+
COMPLEX_SET(
106+
DemoMetaData("COMPLEX_SET", "SHORT_DESCRIPTION", "LONG_DESCRIPTION", listOf("TAGS"), listOf()),
107+
{ it.generateComplexSet() }
108+
);
109+
110+
fun getMetaData(): DemoMetaData = metaData
111+
112+
fun getDemoData(): DemoData = DemoData(metaData, requestFunction(this))
113+
114+
fun generateBasicDemoData(): ModelRequest<TimetableInput, TimetableConfigOverrides> {
115+
return TODO("Generate basic request.")
116+
}
117+
118+
fun generateComplexSet(): ModelRequest<TimetableInput, TimetableConfigOverrides> {
119+
return TODO("Generate complex request.")
120+
}
121+
}
122+
123+
override fun demoMetaData(): List<DemoMetaData> {
124+
return DemoDataKind.entries.map { it.getMetaData() }
125+
}
126+
127+
override fun generateDemoData(demoDataId: String): DemoData {
128+
return DemoDataKind.fromString(demoDataId).getDemoData()
129+
}
130+
}
131+
----
132+
--
133+
====
82134

83135
With this interface implemented, Timefold Solver will automatically expose these methods as REST endpoints:
84136

docs/src/modules/ROOT/pages/service/exposing-metrics.adoc

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ It is therefore necessary to add xref:./rest-api.adoc#openAPISpecification[OpenA
2222
====
2323

2424
.Example for School Timetabling
25+
[tabs]
26+
====
27+
Java::
28+
+
29+
--
2530
[source,java,options="nowrap"]
2631
----
2732
public record TimetableInputMetrics(
@@ -33,10 +38,33 @@ public record TimetableInputMetrics(
3338
type = SchemaType.NUMBER, example = "30", readOnly = true) int timeslots
3439
) implements ModelInputMetrics {}
3540
----
41+
--
42+
43+
Kotlin::
44+
+
45+
--
46+
[source,kotlin,options="nowrap"]
47+
----
48+
data class TimetableInputMetrics(
49+
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons",
50+
format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.",
51+
type = SchemaType.NUMBER, example = "10", readOnly = true) val lessons: Int,
52+
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "timeslots", title = "Timeslots",
53+
format = DataFormat.Values.NUMBER, description = "The number of timeslots submitted in the input dataset.",
54+
type = SchemaType.NUMBER, example = "30", readOnly = true) val timeslots: Int
55+
) : ModelInputMetrics
56+
----
57+
--
58+
====
3659

3760
Next, the `SolverModel` should implement the `InputMetricsAware` interface and construct the defined `ModelInputMetrics` object.
3861

3962
.Example for School Timetabling
63+
[tabs]
64+
====
65+
Java::
66+
+
67+
--
4068
[source,java,options="nowrap"]
4169
----
4270
@PlanningSolution
@@ -65,6 +93,37 @@ public class Timetable implements SolverModel<HardSoftScore>, InputMetricsAware<
6593
// other Getters/Setters/Constructors excluded
6694
}
6795
----
96+
--
97+
98+
Kotlin::
99+
+
100+
--
101+
[source,kotlin,options="nowrap"]
102+
----
103+
@PlanningSolution
104+
class Timetable : SolverModel<HardSoftScore>, InputMetricsAware<TimetableInputMetrics> {
105+
106+
@ProblemFactCollectionProperty
107+
@ValueRangeProvider
108+
val timeslots: List<Timeslot> = emptyList()
109+
110+
@PlanningEntityCollectionProperty
111+
val lessons: List<Lesson> = emptyList()
112+
113+
private var _score: HardSoftScore? = null
114+
115+
@PlanningScore
116+
override fun getScore(): HardSoftScore? = _score
117+
118+
override fun getInputMetrics(): TimetableInputMetrics {
119+
return TimetableInputMetrics(lessons.size, timeslots.size)
120+
}
121+
122+
// other Getters/Setters/Constructors excluded
123+
}
124+
----
125+
--
126+
====
68127

69128
[#modelOutputMetrics]
70129
== Output metrics
@@ -82,6 +141,11 @@ It is therefore necessary to add xref:./rest-api.adoc#openAPISpecification[OpenA
82141
====
83142

84143
.Example for School Timetabling
144+
[tabs]
145+
====
146+
Java::
147+
+
148+
--
85149
[source,java,options="nowrap"]
86150
----
87151
public record TimetableOutputMetrics(
@@ -93,10 +157,33 @@ public record TimetableOutputMetrics(
93157
type = SchemaType.NUMBER, example = "3", readOnly = true) int maxConsecutiveLessons
94158
) implements ModelOutputMetrics {}
95159
----
160+
--
161+
162+
Kotlin::
163+
+
164+
--
165+
[source,kotlin,options="nowrap"]
166+
----
167+
data class TimetableOutputMetrics(
168+
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons",
169+
format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.",
170+
type = SchemaType.NUMBER, example = "0", readOnly = true) val unassignedLessons: Int,
171+
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "maxConsecutiveLessons", title = "Max consecutive lessons",
172+
format = DataFormat.Values.NUMBER, description = "The maximum number of consecutive lessons assigned to any single teacher.",
173+
type = SchemaType.NUMBER, example = "3", readOnly = true) val maxConsecutiveLessons: Int
174+
) : ModelOutputMetrics
175+
----
176+
--
177+
====
96178

97179
Next, the `SolverModel` should implement the `OutputMetricsAware` interface and construct the defined `ModelOutputMetrics` object from the solved state.
98180

99181
.Example for School Timetabling
182+
[tabs]
183+
====
184+
Java::
185+
+
186+
--
100187
[source,java,options="nowrap"]
101188
----
102189
@PlanningSolution
@@ -129,12 +216,50 @@ public class Timetable implements SolverModel<HardSoftScore>, OutputMetricsAware
129216
// other Getters/Setters/Constructors excluded
130217
}
131218
----
219+
--
220+
221+
Kotlin::
222+
+
223+
--
224+
[source,kotlin,options="nowrap"]
225+
----
226+
@PlanningSolution
227+
class Timetable : SolverModel<HardSoftScore>, OutputMetricsAware<TimetableOutputMetrics> {
228+
229+
@ProblemFactCollectionProperty
230+
@ValueRangeProvider
231+
val timeslots: List<Timeslot> = emptyList()
232+
233+
@PlanningEntityCollectionProperty
234+
val lessons: List<Lesson> = emptyList()
235+
236+
private var _score: HardSoftScore? = null
237+
238+
@PlanningScore
239+
override fun getScore(): HardSoftScore? = _score
240+
241+
override fun getOutputMetrics(): TimetableOutputMetrics {
242+
val unassigned = lessons.count { it.timeslot == null || it.room == null }
243+
val maxConsecutive = computeMaxConsecutiveLessons(lessons)
244+
return TimetableOutputMetrics(unassigned, maxConsecutive)
245+
}
246+
247+
// other Getters/Setters/Constructors excluded
248+
}
249+
----
250+
--
251+
====
132252

133253
[#combiningMetrics]
134254
== Combining input and output metrics
135255

136256
A `SolverModel` can implement both `InputMetricsAware` and `OutputMetricsAware` at the same time.
137257

258+
[tabs]
259+
====
260+
Java::
261+
+
262+
--
138263
[source,java,options="nowrap"]
139264
----
140265
@PlanningSolution
@@ -146,3 +271,21 @@ public class Timetable implements SolverModel<HardSoftScore>,
146271
147272
}
148273
----
274+
--
275+
276+
Kotlin::
277+
+
278+
--
279+
[source,kotlin,options="nowrap"]
280+
----
281+
@PlanningSolution
282+
class Timetable : SolverModel<HardSoftScore>,
283+
InputMetricsAware<TimetableInputMetrics>,
284+
OutputMetricsAware<TimetableOutputMetrics> {
285+
286+
// fields, getInputMetrics(), getOutputMetrics() excluded
287+
288+
}
289+
----
290+
--
291+
====

0 commit comments

Comments
 (0)