├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── github │ │ └── vlsi │ │ └── confplanner │ │ ├── fx │ │ ├── ErrorController.java │ │ ├── MainController.java │ │ ├── Planner.java │ │ ├── ScheduleController.java │ │ └── SolverAction.java │ │ ├── model │ │ ├── ConferenceData.java │ │ ├── Day.java │ │ ├── Grade.java │ │ ├── Language.java │ │ ├── NamedEntity.java │ │ ├── ObjectMapperFactory.java │ │ ├── Room.java │ │ ├── RoomTimeslot.java │ │ ├── RoomsTimeslots.java │ │ ├── SpaceCleaner.java │ │ ├── Speaker.java │ │ ├── Talk.java │ │ ├── TalkPlace.java │ │ ├── TalkSequence.java │ │ ├── Timeslot.java │ │ └── Topic.java │ │ ├── solver │ │ ├── ComplexityScorer.java │ │ ├── ExpectedNumListeners.java │ │ ├── LanguageDiversityScorer.java │ │ ├── ScoringParams.java │ │ ├── SlotTalkListeners.java │ │ ├── TalkConflict.java │ │ ├── TalkPlacement.java │ │ ├── TalkPlacementDifficultyComparator.java │ │ ├── TalkPlacementMoveableFilter.java │ │ ├── TalkSpeakerConflict.java │ │ ├── TalkTopicConflict.java │ │ └── TaskPlacementMoveFilter.java │ │ └── votes │ │ ├── FlatVoteReader.java │ │ ├── FlatVotes.java │ │ ├── ParseFirstSurvey.java │ │ └── VoteTitleParser.java └── resources │ ├── com │ └── github │ │ └── vlsi │ │ └── confplanner │ │ ├── fx │ │ ├── Error.fxml │ │ ├── main.fxml │ │ └── schedule.fxml │ │ └── solver │ │ ├── confScheduleScoreRules.drl │ │ └── confScheduleSolverConfig.xml │ └── log4j2.xml └── test └── java └── com └── github └── vlsi └── confplanner └── votes ├── FlatVoteReaderTest.java └── VoteTitleParserTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | data/ 3 | target/ 4 | coverage-report/ 5 | dependency-reduced-pom.xml 6 | /source/revision.txt 7 | *.orig 8 | 9 | /it-dist-check/logs/ 10 | /logs/ 11 | 12 | #IDE projects 13 | .idea/ 14 | *.iml 15 | 16 | # MacOS cache file 17 | .DS_Store 18 | 19 | # Windows image cache files 20 | Thumbs.db 21 | /bin 22 | /.classpath 23 | /.project 24 | /.settings/org.eclipse.core.resources.prefs 25 | /.settings/org.eclipse.m2e.core.prefs 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conference planner 2 | ================== 3 | 4 | Archived 5 | -------- 6 | 7 | The repository has been archived since the tool has been abandoned/migrated. 8 | 9 | 10 | About 11 | ----- 12 | This project helps to plan conference schedules via [OptaPlanner](http://www.optaplanner.org/). 13 | 14 | 15 | Installation 16 | ------------ 17 | 18 | Download a jar from releases, and execute as follows: 19 | 20 | java -jar planner-core-x.y.x.jar 21 | 22 | Input data 23 | ---------- 24 | 25 | The input data is YAML of the following structure: 26 | 27 | ```yaml 28 | capacity: 400 29 | languages: 30 | - name: ru 31 | - name: en 32 | rooms: 33 | - name: 1 34 | capacity: 300 35 | - name: 2 36 | capacity: 200 37 | days: 38 | - name: 1 39 | date: 2017-10-04 40 | timeslots: 41 | - name: 1 42 | day: 1 43 | start: 11:00 44 | duration: 50 45 | - name: 2 46 | day: 1 47 | start: 14:00 48 | duration: 50 49 | - name: 3 50 | day: 1 51 | start: 18:00 52 | duration: 50 53 | topics: 54 | - name: Case study 55 | - name: Tricks 56 | speakers: 57 | - name: Speaker A 58 | arriveTime: 2017-10-04T12:00:00+03:00 59 | - name: Speaker B 60 | leaveTime: 2017-10-04T16:00:00+03:00 61 | talks: 62 | - name: 'How to arrive late' 63 | language: en 64 | speakers: Speaker A 65 | topics: Tricks 66 | - name: 'How to depart early' 67 | language: ru 68 | speakers: Speaker B 69 | topics: Tricks 70 | - name: 'Coffee time' 71 | language: en 72 | speakers: 73 | - Speaker A 74 | - Speaker B 75 | topics: 76 | - Case study 77 | - Tricks 78 | ``` 79 | 80 | License 81 | ------- 82 | Apache 2 License 83 | 84 | Author 85 | ------ 86 | Vladimir Sitnikov 87 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.vlsi.confplanner 8 | planner-core 9 | 1.0.2 10 | 11 | 12 | UTF-8 13 | 1.8 14 | 1.8 15 | 16 | 17 | 18 | 19 | org.apache.logging.log4j 20 | log4j-api 21 | 2.16.0 22 | 23 | 24 | org.apache.logging.log4j 25 | log4j-core 26 | 2.16.0 27 | 28 | 29 | org.apache.logging.log4j 30 | log4j-slf4j-impl 31 | 2.16.0 32 | 33 | 34 | org.optaplanner 35 | optaplanner-core 36 | 7.3.0.Final 37 | 38 | 39 | com.fasterxml.jackson.core 40 | jackson-core 41 | 2.9.0 42 | 43 | 44 | com.fasterxml.jackson.dataformat 45 | jackson-dataformat-yaml 46 | 2.9.0 47 | 48 | 49 | com.fasterxml.jackson.dataformat 50 | jackson-dataformat-csv 51 | 2.9.0 52 | 53 | 54 | com.fasterxml.jackson.core 55 | jackson-databind 56 | 2.9.10.8 57 | 58 | 59 | com.fasterxml.jackson.datatype 60 | jackson-datatype-jsr310 61 | 2.9.8 62 | 63 | 64 | org.testng 65 | testng 66 | 6.11 67 | test 68 | 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-jar-plugin 76 | 77 | 78 | 79 | com.github.vlsi.confplanner.fx.Planner 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-shade-plugin 87 | 88 | 89 | package 90 | 91 | shade 92 | 93 | 94 | 95 | 96 | META-INF/kie.conf 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/fx/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.fx; 2 | 3 | import javafx.fxml.FXML; 4 | import javafx.scene.control.Label; 5 | 6 | public class ErrorController { 7 | @FXML 8 | private Label errorMessage; 9 | 10 | public void setErrorText(String text) { 11 | errorMessage.setText(text); 12 | } 13 | 14 | @FXML 15 | private void close() { 16 | errorMessage.getScene().getWindow().hide(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/fx/MainController.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.fx; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.vlsi.confplanner.model.*; 6 | import com.github.vlsi.confplanner.votes.FlatVoteReader; 7 | import com.github.vlsi.confplanner.votes.FlatVotes; 8 | import javafx.event.ActionEvent; 9 | import javafx.fxml.FXMLLoader; 10 | import javafx.scene.Node; 11 | import javafx.scene.Parent; 12 | import javafx.scene.Scene; 13 | import javafx.stage.FileChooser; 14 | import javafx.stage.Stage; 15 | 16 | import java.io.BufferedWriter; 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.net.MalformedURLException; 20 | import java.net.URL; 21 | import java.nio.charset.StandardCharsets; 22 | import java.nio.file.Files; 23 | import java.nio.file.StandardOpenOption; 24 | import java.time.LocalDate; 25 | import java.time.LocalTime; 26 | import java.util.*; 27 | import java.util.regex.Matcher; 28 | import java.util.regex.Pattern; 29 | import java.util.stream.Collectors; 30 | 31 | public class MainController { 32 | public void parseFirstVotes(ActionEvent actionEvent) { 33 | final FileChooser fileChooser = new FileChooser(); 34 | fileChooser.setTitle("CSV with the results of the first survey"); 35 | fileChooser.getExtensionFilters() 36 | .addAll( 37 | new FileChooser.ExtensionFilter("csv", "*.csv") 38 | ); 39 | Node source = (Node) actionEvent.getSource(); 40 | File file = fileChooser.showOpenDialog(source.getScene().getWindow()); 41 | if (file == null) { 42 | return; 43 | } 44 | 45 | 46 | List votes = null; 47 | try { 48 | votes = new FlatVoteReader(file.toURL()).get(); 49 | } catch (MalformedURLException e) { 50 | throw new IllegalArgumentException("Unable to open file " + file, e); 51 | } 52 | 53 | Set speakers = new HashSet<>(); 54 | Set talks = new HashSet<>(); 55 | for (FlatVotes vote : votes) { 56 | for (Talk talk : vote.getTalks()) { 57 | speakers.addAll(talk.getSpeakers()); 58 | } 59 | talks.addAll(vote.getTalks()); 60 | } 61 | List languages = talks.stream().map(Talk::getLanguage).distinct().collect(Collectors.toList()); 62 | ConferenceData d = new ConferenceData(); 63 | d.setCapacity(500); 64 | Day d1 = new Day("1", LocalDate.of(2000, 1, 1)); 65 | d.setDays(Arrays.asList(d1)); 66 | d.setRooms(Arrays.asList(new Room("1", 400, false), new Room("2", 300, false), new Room("3", 200, false))); 67 | d.setTimeslots(Arrays.asList( 68 | new Timeslot("1", d1, LocalTime.of(11, 40), 50), 69 | new Timeslot("2", d1, LocalTime.of(12, 50), 50), 70 | new Timeslot("3", d1, LocalTime.of(14, 25), 50), 71 | new Timeslot("4", d1, LocalTime.of(15, 35), 50), 72 | new Timeslot("5", d1, LocalTime.of(16, 45), 50) 73 | )); 74 | d.setSpeakers(new ArrayList<>(speakers)); 75 | d.setTalks(new ArrayList<>(talks)); 76 | d.setTalkPositions(Collections.emptyList()); 77 | d.setLanguages(languages); 78 | d.setVotes(votes); 79 | d.sort(); 80 | 81 | ObjectMapper mapper = ObjectMapperFactory.getInstance(); 82 | String s; 83 | try { 84 | s = mapper.writeValueAsString(d); 85 | } catch (JsonProcessingException e) { 86 | throw new IllegalStateException("Unable to convert ConferenceData " + d + "to model", e); 87 | } 88 | 89 | String surveyName = file.getName(); 90 | String outputName; 91 | Matcher matcher = Pattern.compile("(.*?) Формируем").matcher(surveyName); 92 | if (matcher.lookingAt()) { 93 | outputName = matcher.group(1).replaceAll(" +", "_") + ".yml"; 94 | } else { 95 | outputName = surveyName.replaceAll(".csv$", ".yml"); 96 | if (surveyName.equals(outputName)) { 97 | outputName += ".yml"; 98 | } 99 | } 100 | File outputFile = new File(file.getParent(), outputName); 101 | if (outputFile.exists()) { 102 | throw new IllegalArgumentException("File " + outputName + "(" + outputFile.getAbsolutePath() + ") already exists"); 103 | } 104 | if (outputFile.isDirectory()) { 105 | throw new IllegalArgumentException("File " + outputName + "(" + outputFile.getAbsolutePath() + ") is directory"); 106 | } 107 | 108 | try (BufferedWriter bw = Files.newBufferedWriter(outputFile.toPath(), StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING)) { 109 | bw.write(s); 110 | } catch (IOException e) { 111 | throw new IllegalStateException("Unable to write YAML to " + outputFile.getAbsolutePath(), e); 112 | } 113 | System.out.println(s); 114 | } 115 | 116 | public void createSchedule(ActionEvent actionEvent) throws IOException { 117 | final FileChooser fileChooser = new FileChooser(); 118 | fileChooser.setTitle("Conference YAML info"); 119 | fileChooser.getExtensionFilters() 120 | .addAll( 121 | new FileChooser.ExtensionFilter("yml", "*.yml") 122 | ); 123 | Node source = (Node) actionEvent.getSource(); 124 | File file = fileChooser.showOpenDialog(source.getScene().getWindow()); 125 | if (file == null) { 126 | return; 127 | } 128 | 129 | Stage dialog = new Stage(); 130 | // dialog.initModality(Modality.WINDOW_MODAL); 131 | 132 | URL resource = getClass().getResource("schedule.fxml"); 133 | FXMLLoader loader = new FXMLLoader(resource); 134 | Parent root = loader.load(); 135 | 136 | ScheduleController schedule = loader.getController(); 137 | schedule.setYml(file); 138 | 139 | dialog.setScene(new Scene(root, 800, 600)); 140 | dialog.setTitle(file.getName() + ", " + file.getParentFile().getAbsolutePath()); 141 | dialog.show(); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/fx/Planner.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.fx; 2 | 3 | import javafx.application.Application; 4 | import javafx.application.Platform; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Parent; 7 | import javafx.scene.Scene; 8 | import javafx.stage.Modality; 9 | import javafx.stage.Stage; 10 | 11 | import java.io.IOException; 12 | import java.io.PrintWriter; 13 | import java.io.StringWriter; 14 | 15 | public class Planner extends Application { 16 | 17 | public static void main(String[] args) { 18 | launch(args); 19 | } 20 | 21 | private static void showError(Thread t, Throwable e) { 22 | System.err.println("***Default exception handler***"); 23 | if (Platform.isFxApplicationThread()) { 24 | showErrorDialog(e); 25 | } else { 26 | System.err.println("An unexpected error occurred in " + t); 27 | e.printStackTrace(); 28 | } 29 | } 30 | 31 | private static void showErrorDialog(Throwable e) { 32 | e.printStackTrace(); 33 | StringWriter errorMsg = new StringWriter(); 34 | e.printStackTrace(new PrintWriter(errorMsg)); 35 | Stage dialog = new Stage(); 36 | dialog.initModality(Modality.APPLICATION_MODAL); 37 | FXMLLoader loader = new FXMLLoader(ErrorController.class.getResource("Error.fxml")); 38 | try { 39 | Parent root = loader.load(); 40 | ((ErrorController) loader.getController()).setErrorText(errorMsg.toString()); 41 | dialog.setScene(new Scene(root, 250, 400)); 42 | dialog.show(); 43 | } catch (IOException exc) { 44 | exc.printStackTrace(); 45 | } 46 | } 47 | 48 | @Override 49 | public void start(Stage primaryStage) throws IOException { 50 | Thread.setDefaultUncaughtExceptionHandler(Planner::showError); 51 | 52 | Parent root = FXMLLoader.load(getClass().getResource("main.fxml")); 53 | primaryStage.setTitle("Conference planner"); 54 | primaryStage.setScene(new Scene(root)); 55 | primaryStage.show(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/fx/ScheduleController.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.fx; 2 | 3 | import javafx.animation.Animation; 4 | import javafx.animation.KeyFrame; 5 | import javafx.animation.Timeline; 6 | import javafx.event.ActionEvent; 7 | import javafx.scene.control.Button; 8 | import javafx.scene.control.Label; 9 | import javafx.scene.control.ProgressBar; 10 | import javafx.scene.control.Spinner; 11 | import javafx.scene.web.WebView; 12 | import javafx.util.Duration; 13 | 14 | import java.io.File; 15 | import java.io.PrintWriter; 16 | import java.io.StringWriter; 17 | import java.util.concurrent.*; 18 | 19 | public class ScheduleController { 20 | public WebView results; 21 | public Spinner duration; 22 | public Button start; 23 | public ProgressBar progress; 24 | public Label statusLabel; 25 | public Spinner capacityFactor; 26 | 27 | private File yml; 28 | 29 | private ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() { 30 | @Override 31 | public Thread newThread(Runnable r) { 32 | Thread t = new Thread(r); 33 | t.setName("Process " + yml.getAbsolutePath()); 34 | t.setDaemon(true); 35 | return t; 36 | } 37 | }); 38 | private SolverAction currentAction; 39 | private Future currentResult; 40 | private String prevDescription; 41 | private Timeline statusUpdate = new Timeline(new KeyFrame(Duration.seconds(1), 42 | ae -> updateStatus())); 43 | 44 | public void setYml(File yml) { 45 | this.yml = yml; 46 | } 47 | 48 | public synchronized void startStop(ActionEvent event) { 49 | if (currentResult != null && !currentResult.isDone()) { 50 | // stop 51 | currentAction.cancel(); 52 | return; 53 | } 54 | start.setText("Stop"); 55 | currentAction = new SolverAction(yml, ((Number) duration.getValue()).intValue(), ((Number) capacityFactor.getValue()).intValue()); 56 | statusUpdate.setCycleCount(Animation.INDEFINITE); 57 | statusUpdate.play(); 58 | currentResult = executor.submit(currentAction); 59 | } 60 | 61 | public void updateStatus() { 62 | System.out.println("results = " + results); 63 | Future currentResult = this.currentResult; 64 | boolean done = currentResult != null && currentResult.isDone(); 65 | if (done) { 66 | statusUpdate.stop(); 67 | start.setText("Start"); 68 | try { 69 | currentResult.get(1, TimeUnit.MILLISECONDS); 70 | } catch (ExecutionException e) { 71 | Throwable cause = e.getCause(); 72 | cause.printStackTrace(); 73 | String msg = cause.getMessage(); 74 | if (msg == null) { 75 | msg = ""; 76 | } 77 | if (msg.length() > 200) { 78 | msg = msg.substring(0, 200); 79 | } 80 | statusLabel.setText("Failed: " + msg); 81 | StringWriter sw = new StringWriter(); 82 | cause.printStackTrace(new PrintWriter(sw)); 83 | results.getEngine().loadContent(sw.toString(), "text/plain"); 84 | return; 85 | } catch (InterruptedException | TimeoutException e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | SolverAction action = this.currentAction; 90 | double progress = action.getProgress(); 91 | this.progress.setProgress(progress); 92 | statusLabel.setText(done ? "Done" : action.getShortStatus()); 93 | String descr = action.describeBest(); 94 | if (prevDescription == descr) { 95 | return; 96 | } 97 | prevDescription = descr; 98 | results.getEngine().loadContent(descr, "text/html"); 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/fx/SolverAction.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.fx; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.vlsi.confplanner.model.ConferenceData; 6 | import com.github.vlsi.confplanner.model.ObjectMapperFactory; 7 | import org.optaplanner.core.api.score.constraint.ConstraintMatchTotal; 8 | import org.optaplanner.core.api.score.constraint.Indictment; 9 | import org.optaplanner.core.api.solver.Solver; 10 | import org.optaplanner.core.api.solver.SolverFactory; 11 | import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent; 12 | import org.optaplanner.core.api.solver.event.SolverEventListener; 13 | import org.optaplanner.core.impl.score.director.ScoreDirector; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.io.PrintWriter; 18 | import java.io.StringWriter; 19 | import java.util.Collection; 20 | import java.util.Map; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.atomic.AtomicReference; 23 | 24 | public class SolverAction implements Callable { 25 | private static final String SOLVER_CONFIG = "com/github/vlsi/confplanner/solver/confScheduleSolverConfig.xml"; 26 | 27 | private final File yml; 28 | private final int duration; 29 | private final int capacityFactor; 30 | private volatile Solver solver; 31 | private volatile ConferenceData prevBest; 32 | private volatile String prevBestDescr; 33 | private final AtomicReference bestSolution = new AtomicReference<>(); 34 | 35 | public SolverAction(File yml, int duration, int capacityFactor) { 36 | this.yml = yml; 37 | this.duration = duration == 0 ? 1 : duration; 38 | this.capacityFactor = capacityFactor; 39 | } 40 | 41 | @Override 42 | public Boolean call() throws Exception { 43 | ObjectMapper mapper = ObjectMapperFactory.getInstance(); 44 | ConferenceData data; 45 | data = mapper.readValue(yml, ConferenceData.class); 46 | data.setRoomCapacityFactor(capacityFactor); 47 | SolverFactory solverFactory = SolverFactory.createFromXmlResource(SOLVER_CONFIG); 48 | solverFactory.getSolverConfig().getTerminationConfig().setSecondsSpentLimit((long) duration); 49 | solver = solverFactory.buildSolver(); 50 | solver.addEventListener(new SolverEventListener() { 51 | @Override 52 | public void bestSolutionChanged(BestSolutionChangedEvent event) { 53 | bestSolution.set(event.getNewBestSolution()); 54 | } 55 | }); 56 | solver.solve(data); 57 | System.out.println("solver.getBestScore() = " + solver.getBestScore()); 58 | return true; 59 | } 60 | 61 | public void cancel() { 62 | Solver solver = this.solver; 63 | if (solver != null && !solver.isSolving()) { 64 | solver.terminateEarly(); 65 | } 66 | } 67 | 68 | public double getProgress() { 69 | Solver solver = this.solver; 70 | return solver == null ? 0 : (solver.getTimeMillisSpent() * 0.001 / duration); 71 | } 72 | 73 | public String getShortStatus() { 74 | Solver solver = this.solver; 75 | String status = solver == null || !solver.isSolving() ? "idle" : "solving"; 76 | if (solver != null) { 77 | status += ", best score " + solver.getBestScore(); 78 | } 79 | return status; 80 | } 81 | 82 | public String describeBest() { 83 | ConferenceData currentBest = bestSolution.get(); 84 | if (currentBest == null) { 85 | return prevBestDescr; 86 | } 87 | bestSolution.compareAndSet(currentBest, null); 88 | Solver solver = this.solver; 89 | if (solver == null) { 90 | return ""; 91 | } 92 | prevBest = currentBest; 93 | currentBest.sort(); 94 | 95 | StringWriter sw = new StringWriter(); 96 | 97 | ScoreDirector scorer = solver.getScoreDirectorFactory().buildScoreDirector(); 98 | scorer.setWorkingSolution(currentBest); 99 | Collection totals = scorer.getConstraintMatchTotals(); 100 | Map map = scorer.getIndictmentMap(); 101 | 102 | sw.append(""); 103 | for (ConstraintMatchTotal total : totals) { 104 | sw.append(String.valueOf(total.getScoreTotal())).append("\t").append(total.getConstraintName()).append("
"); 105 | } 106 | sw.append("
"); 107 | currentBest.print(sw, map); 108 | 109 | // ObjectMapper mapper = ObjectMapperFactory.getInstance(); 110 | // String s; 111 | // try { 112 | // sw.append("
");
113 | //            mapper.writeValue(sw, currentBest.getTalkPlacements());
114 | //            sw.append("
"); 115 | // } catch (IOException e) { 116 | // e.printStackTrace(new PrintWriter(sw)); 117 | // } 118 | 119 | sw.append(""); 120 | prevBestDescr = sw.toString(); 121 | return prevBestDescr; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/ConferenceData.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import com.github.vlsi.confplanner.solver.ExpectedNumListeners; 7 | import com.github.vlsi.confplanner.solver.ScoringParams; 8 | import com.github.vlsi.confplanner.solver.TalkConflict; 9 | import com.github.vlsi.confplanner.solver.TalkPlacement; 10 | import com.github.vlsi.confplanner.votes.FlatVotes; 11 | import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty; 12 | import org.optaplanner.core.api.domain.solution.PlanningScore; 13 | import org.optaplanner.core.api.domain.solution.PlanningSolution; 14 | import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty; 15 | import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty; 16 | import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; 17 | import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; 18 | import org.optaplanner.core.api.score.constraint.ConstraintMatch; 19 | import org.optaplanner.core.api.score.constraint.Indictment; 20 | 21 | import java.io.PrintWriter; 22 | import java.io.Writer; 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Collection; 26 | import java.util.Collections; 27 | import java.util.Comparator; 28 | import java.util.HashMap; 29 | import java.util.HashSet; 30 | import java.util.LinkedHashMap; 31 | import java.util.LinkedHashSet; 32 | import java.util.List; 33 | import java.util.Map; 34 | import java.util.Set; 35 | import java.util.concurrent.atomic.AtomicLong; 36 | import java.util.function.Function; 37 | import java.util.stream.Collectors; 38 | 39 | @JsonPropertyOrder({"capacity", "languages", "rooms", "days", "timeslots", "roomAvailability", "roomUnavailability", "topics", "speakers", "talks", "talkSequence", "talkPlacements"}) 40 | @PlanningSolution 41 | public class ConferenceData { 42 | private int capacity; 43 | private List speakers; 44 | private List rooms; 45 | private List languages; 46 | private List topics; 47 | private List talks; 48 | private List timeslots; 49 | private List days; 50 | private List roomAvailability; 51 | private List roomUnavailability; 52 | // @JsonIgnore 53 | private List talkPlacements; 54 | private List expectedNumListeners; 55 | private Collection computedListeners; 56 | private List talkSequence; 57 | 58 | @JsonIgnore 59 | private List grades = Arrays.asList( 60 | new Grade("200"), 61 | new Grade("300"), 62 | new Grade("400") 63 | ); 64 | 65 | private List votes; 66 | 67 | // @JsonIgnore 68 | private List talkPositions; 69 | 70 | @JsonIgnore 71 | private Map talkMap; 72 | @JsonIgnore 73 | private Map talkPlacementMap; 74 | @JsonIgnore 75 | private Set preassignedSlots = new HashSet<>(); 76 | 77 | // planning 78 | private HardSoftScore score; 79 | @JsonIgnore 80 | private Map allExpectedListeners; 81 | @JsonIgnore 82 | private Collection talkConflicts; 83 | private int roomCapacityFactor; 84 | 85 | @ProblemFactCollectionProperty 86 | public List getGrades() { 87 | return grades; 88 | } 89 | 90 | public int getCapacity() { 91 | return capacity; 92 | } 93 | 94 | public void setCapacity(int capacity) { 95 | this.capacity = capacity; 96 | } 97 | 98 | public List getSpeakers() { 99 | return speakers; 100 | } 101 | 102 | public void setSpeakers(List speakers) { 103 | this.speakers = speakers; 104 | } 105 | 106 | @ValueRangeProvider(id = "roomsProvider") 107 | @ProblemFactCollectionProperty 108 | public List getRooms() { 109 | return rooms; 110 | } 111 | 112 | public void setRooms(List rooms) { 113 | this.rooms = rooms; 114 | } 115 | 116 | @ProblemFactCollectionProperty 117 | public List getTalks() { 118 | return talks; 119 | } 120 | 121 | @JsonProperty 122 | public void setTalks(List talks) { 123 | this.talks = talks; 124 | this.talkPlacementMap = talks.stream().collect(Collectors.toMap(Function.identity(), TalkPlacement::new)); 125 | this.talkPlacements = new ArrayList<>(this.talkPlacementMap.values()); 126 | this.talkMap = talks.stream().collect(Collectors.toMap(Function.identity(), Function.identity())); 127 | } 128 | 129 | public List getLanguages() { 130 | return languages; 131 | } 132 | 133 | public void setLanguages(List languages) { 134 | this.languages = languages; 135 | } 136 | 137 | @ProblemFactCollectionProperty 138 | public List getTopics() { 139 | return topics; 140 | } 141 | 142 | public void setTopics(List topics) { 143 | this.topics = topics; 144 | } 145 | 146 | @ValueRangeProvider(id = "timeslotsProvider") 147 | @ProblemFactCollectionProperty 148 | public List getTimeslots() { 149 | return timeslots; 150 | } 151 | 152 | public void setTimeslots(List timeslots) { 153 | this.timeslots = timeslots; 154 | } 155 | 156 | @ProblemFactCollectionProperty 157 | public List getDays() { 158 | return days; 159 | } 160 | 161 | public void setDays(List days) { 162 | this.days = days; 163 | } 164 | 165 | public List getRoomAvailability() { 166 | return roomAvailability; 167 | } 168 | 169 | public void setRoomAvailability(List roomAvailability) { 170 | this.roomAvailability = roomAvailability; 171 | } 172 | 173 | public List getRoomUnavailability() { 174 | return roomUnavailability; 175 | } 176 | 177 | public void setRoomUnavailability(List roomUnavailability) { 178 | this.roomUnavailability = roomUnavailability; 179 | } 180 | 181 | public List getExpectedNumListeners() { 182 | return expectedNumListeners; 183 | } 184 | 185 | public void setExpectedNumListeners(List expectedNumListeners) { 186 | this.expectedNumListeners = expectedNumListeners; 187 | } 188 | 189 | public List getTalkPositions() { 190 | return talkPositions; 191 | } 192 | 193 | public void setTalkPositions(List talkPositions) { 194 | this.talkPositions = talkPositions; 195 | for (TalkPlace p : talkPositions) { 196 | TalkPlacement place = talkPlacementMap.get(p.getTalk()); 197 | RoomTimeslot rts = new RoomTimeslot(p.getRoom(), p.getSlot()); 198 | preassignedSlots.add(rts); 199 | place.setRoomTimeslot(rts); 200 | place.setMoveable(false); 201 | } 202 | } 203 | 204 | // Planning 205 | @PlanningScore 206 | public HardSoftScore getScore() { 207 | return score; 208 | } 209 | 210 | public void setScore(HardSoftScore score) { 211 | this.score = score; 212 | } 213 | 214 | @ProblemFactCollectionProperty 215 | public List getTalkSequence() { 216 | return talkSequence == null ? Collections.emptyList() : talkSequence; 217 | } 218 | 219 | public void setTalkSequence(List talkSequence) { 220 | this.talkSequence = talkSequence; 221 | for (TalkSequence sequence : talkSequence) { 222 | talkPlacementMap.get(sequence.getFirst()).setHasSequence(true); 223 | talkPlacementMap.get(sequence.getSecond()).setHasSequence(true); 224 | } 225 | } 226 | 227 | @ProblemFactCollectionProperty 228 | public Collection getAllExpectedListeners() { 229 | return allExpectedListeners.values(); 230 | } 231 | 232 | @PlanningEntityCollectionProperty 233 | @JsonIgnore 234 | public List getTalkPlacements() { 235 | return talkPlacements; 236 | } 237 | 238 | @ValueRangeProvider(id = "roomTimeslots") 239 | @ProblemFactCollectionProperty 240 | @JsonIgnore 241 | public List getAllRooms() { 242 | Set res = new LinkedHashSet<>(); 243 | Map> customAvailable = groupSlots(getRoomAvailability()); 244 | Map> customUnavailable = groupSlots(getRoomUnavailability()); 245 | for (Room room : getRooms()) { 246 | List timeslots = customAvailable.get(room); 247 | if (timeslots != null) { 248 | res.addAll(timeslots); 249 | } else { 250 | for (Timeslot timeslot : getTimeslots()) { 251 | res.add(new RoomTimeslot(room, timeslot)); 252 | } 253 | } 254 | timeslots = customUnavailable.get(room); 255 | if (timeslots != null) { 256 | res.removeAll(timeslots); 257 | } 258 | } 259 | res.removeAll(preassignedSlots); 260 | return new ArrayList<>(res); 261 | } 262 | 263 | private Map> groupSlots(List available) { 264 | if (available == null) { 265 | return Collections.emptyMap(); 266 | } 267 | Map> res = new HashMap<>(); 268 | for (RoomsTimeslots roomsTimeslots : available) { 269 | for (Room room : roomsTimeslots.getRooms()) { 270 | List slots = res.computeIfAbsent(room, (Room r) -> new ArrayList()); 271 | for (Timeslot timeslot : roomsTimeslots.getTimeslots()) { 272 | RoomTimeslot rts = new RoomTimeslot(room, timeslot); 273 | if (!slots.contains(rts)) { 274 | slots.add(rts); 275 | } 276 | } 277 | } 278 | } 279 | return res; 280 | } 281 | 282 | public List getVotes() { 283 | return votes; 284 | } 285 | 286 | public void setVotes(List votes) { 287 | this.votes = votes; 288 | 289 | Map expectedListeners = new HashMap<>(); 290 | 291 | int timeslotsCount = timeslots.size(); 292 | 293 | int totalVotes = votes.stream().mapToInt(FlatVotes::getCount).sum(); 294 | 295 | Map conflicts = new HashMap<>(); 296 | for (FlatVotes flat : votes) { 297 | Collection talks = flat.getTalks(); 298 | if (talkMap != null) { 299 | List list = new ArrayList<>(); 300 | for (Talk talk : talks) { 301 | Talk talk1 = talkMap.get(talk); 302 | if (talk1 != null) { 303 | list.add(talk1); 304 | } 305 | } 306 | talks = list; 307 | } 308 | // List list = new ArrayList<>(); 309 | // for (Talk talk : talks) { 310 | // Talk talk1 = talkMap.get(talk); 311 | // list.add(talk1); 312 | // } 313 | // talks = list; 314 | double kConflicts = 1.0 * flat.getCount() * getCapacity() / totalVotes;// / (talks.size() * (talks.size() - 1) / 2); 315 | double kListeners = 1.0 * flat.getCount() * getCapacity() * timeslotsCount / totalVotes / talks.size(); 316 | for (Talk a : talks) { 317 | expectedListeners.computeIfAbsent(a, ExpectedNumListeners::create).inc(kListeners); 318 | if (talks.size() == 1) { 319 | continue; 320 | } 321 | for (Talk b : talks) { 322 | if (a.getName().compareTo(b.getName()) >= 0) { 323 | continue; 324 | } 325 | TalkConflict key = new TalkConflict(a, b); 326 | conflicts.computeIfAbsent(key, x -> key).inc(kConflicts); 327 | } 328 | } 329 | } 330 | talkConflicts = conflicts.values(); 331 | computedListeners = expectedListeners.values(); 332 | allExpectedListeners = computeAllExpectedListeners(); 333 | if (talkMap == null) { 334 | return; 335 | } 336 | for (ExpectedNumListeners e : allExpectedListeners.values()) { 337 | Talk talk = talkMap.get(e.getTalk()); 338 | if (talk == null) { 339 | continue; 340 | } 341 | talk.setNumListeners((int) e.getCount()); 342 | } 343 | } 344 | 345 | private Map computeAllExpectedListeners() { 346 | Map res = new HashMap<>(); 347 | if (this.expectedNumListeners != null) { 348 | for (ExpectedNumListeners e : expectedNumListeners) { 349 | res.put(e.getTalk(), e); 350 | } 351 | } 352 | 353 | if (computedListeners != null) { 354 | for (ExpectedNumListeners computedListener : computedListeners) { 355 | Talk talk = computedListener.getTalk(); 356 | if (!res.containsKey(talk)) { 357 | res.put(talk, computedListener); 358 | } 359 | } 360 | } 361 | 362 | return res; 363 | } 364 | 365 | @ProblemFactCollectionProperty 366 | public Collection getTalkConflicts() { 367 | return talkConflicts; 368 | } 369 | 370 | public void sort() { 371 | speakers.sort(Comparator.comparing(Speaker::getName)); 372 | if (topics != null) { 373 | topics.sort(Comparator.comparing(Topic::getName)); 374 | } 375 | if (languages != null) { 376 | languages.sort(Comparator.comparing(Language::getName)); 377 | } 378 | if (votes != null) { 379 | votes.sort(Comparator.naturalOrder()); 380 | } 381 | if (timeslots != null) { 382 | timeslots.sort(Comparator.comparing(Timeslot::getStartTimestamp)); 383 | } 384 | if (rooms != null) { 385 | rooms.sort(Comparator.comparing(Room::getName)); 386 | } 387 | if (talkPlacements != null) { 388 | talkPlacements.sort(Comparator.comparing(TalkPlacement::getSlot, Comparator.nullsFirst(Comparator.naturalOrder())) 389 | .thenComparing(TalkPlacement::getRoom, Comparator.nullsFirst(Comparator.naturalOrder()))); 390 | } 391 | } 392 | 393 | public void print(Writer w, Map map) { 394 | PrintWriter pw = new PrintWriter(w); 395 | Timeslot prevSlot = null; 396 | Map expectedCount = new HashMap<>(); 397 | for (TalkPlacement p : talkPlacements) { 398 | expectedCount.compute(p.getSlot(), (slot, val) -> (val == null ? 0 : val) + p.getExpectedNumListeners()); 399 | } 400 | 401 | Map> slotTalks = new LinkedHashMap<>(); 402 | for (TalkPlacement talk : talkPlacements) { 403 | Timeslot slot = talk.getSlot(); 404 | slotTalks.computeIfAbsent(slot, k -> new ArrayList<>()).add(talk); 405 | } 406 | 407 | printTalks(pw, slotTalks, p -> p.getTalk().getSpeakers() 408 | .stream() 409 | .map(NamedEntity::getName) 410 | .collect(Collectors.joining(", "))); 411 | pw.println("
"); 412 | printTalks(pw, slotTalks, p -> p.getTalk().getFullTitle()); 413 | pw.println("
"); 414 | printTalks(pw, slotTalks, p -> p.getTalk().getLanguage().getName()); 415 | pw.println("
"); 416 | printTalks(pw, slotTalks, p -> p.getTalk().getTopics() == null ? "" : p.getTalk().getTopics() 417 | .stream() 418 | .map(NamedEntity::getName) 419 | .collect(Collectors.joining(", "))); 420 | pw.println("
Num listeners"); 421 | printTalks(pw, slotTalks, p -> String.valueOf(p.getTalk().getNumListeners())); 422 | pw.println("
Complexity"); 423 | printTalks(pw, slotTalks, p -> p.getTalk().getComplexity()); 424 | pw.println("
"); 425 | 426 | for (TalkPlacement talk : talkPlacements) { 427 | Timeslot slot = talk.getSlot(); 428 | if (prevSlot != null && slot.minus(prevSlot).toHours() > 8) { 429 | pw.println("
"); 430 | } 431 | if (prevSlot != slot) { 432 | pw.println("
"); 433 | pw.println("

" + slot + ", " + expectedCount.get(slot) + " listeners expected


"); 434 | 435 | // if (map != null) { 436 | // Indictment indictment = map.get(slot); 437 | // } 438 | prevSlot = slot; 439 | } 440 | 441 | // slotTalks.add(talk); 442 | Room room = talk.getRoom(); 443 | try { 444 | String roomDescr; 445 | if (room == null) { 446 | roomDescr = "noroom"; 447 | } else { 448 | roomDescr = room.getName() + ":" + room.getCapacity(); 449 | } 450 | 451 | pw.println("    " + roomDescr 452 | + "(" + Math.round(talk.getExpectedNumListeners()) + ")" 453 | + ", " + talk.getTalk().getLanguage().getName() 454 | + ", " + (talk.getTalk().getTopics() == null ? "" : talk.getTalk().getTopics().stream().map(Topic::getName).collect(Collectors.toList())) 455 | + ", " + talk.getTalk().getFullTitle() 456 | + "
"); 457 | } catch (NullPointerException e) { 458 | e.printStackTrace(); 459 | } 460 | if (map == null) { 461 | continue; 462 | } 463 | Indictment indictment = map.get(talk); 464 | if (indictment == null) { 465 | continue; 466 | } 467 | pw.println("        " + indictment.getScoreTotal() + "
"); 468 | for (ConstraintMatch match : indictment.getConstraintMatchSet()) { 469 | String cname = match.getConstraintName(); 470 | // if ("visitMostTalks".equals(cname)) { 471 | // pw.println(" " + ); 472 | // continue; 473 | // } 474 | pw.println("        " + cname + " " + match.getScore() + "
"); 475 | if ("visitMostTalks".equals(cname)) { 476 | pw.print("            "); 477 | Object o = match.getJustificationList().get(2); 478 | String color = null; 479 | if (o instanceof TalkConflict) { 480 | TalkConflict tc = (TalkConflict) o; 481 | if (tc.getCount() > talk.getExpectedNumListeners() / 2) { 482 | color = "red"; 483 | } else if (tc.getCount() < talk.getExpectedNumListeners() / 4) { 484 | color = "darkgreen"; 485 | } 486 | } 487 | if (color != null) { 488 | pw.append(""); 489 | } 490 | pw.print(o); 491 | if (color != null) { 492 | pw.append(""); 493 | } 494 | pw.println("
"); 495 | } 496 | } 497 | } 498 | } 499 | 500 | private void printTalks(PrintWriter pw, Map> slotTalks, 501 | Function converter) { 502 | for (Map.Entry> entry : slotTalks.entrySet()) { 503 | List value = entry.getValue(); 504 | for (int i = 0; i < value.size(); i++) { 505 | TalkPlacement talk = value.get(i); 506 | if (i > 0) { 507 | pw.print('\t'); 508 | } 509 | Room room = talk.getRoom(); 510 | if (room == null) { 511 | pw.print("noroom"); 512 | } else { 513 | pw.print(room.getName()); 514 | } 515 | pw.print(": "); 516 | pw.print('"' + converter.apply(talk) + '"'); 517 | } 518 | pw.println("
"); 519 | } 520 | } 521 | 522 | public void setRoomCapacityFactor(int roomCapacityFactor) { 523 | this.roomCapacityFactor = roomCapacityFactor; 524 | } 525 | 526 | @ProblemFactProperty 527 | @JsonIgnore 528 | public ScoringParams getScoringParams() { 529 | return new ScoringParams(roomCapacityFactor); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Day.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 6 | 7 | import java.time.LocalDate; 8 | 9 | @JsonIdentityInfo(scope = Day.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 10 | public class Day implements Comparable { 11 | private final String name; 12 | private final LocalDate date; 13 | 14 | public Day(@JsonProperty("name") String name, @JsonProperty("date") LocalDate date) { 15 | this.name = name; 16 | this.date = date; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public LocalDate getDate() { 24 | return date; 25 | } 26 | 27 | @Override 28 | public int compareTo(Day o) { 29 | return date.compareTo(o.date); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Day{" + 35 | "name='" + name + '\'' + 36 | '}'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Grade.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | public class Grade extends NamedEntity { 4 | public Grade(String name) { 5 | super(name); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Language.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 6 | 7 | @JsonIdentityInfo(scope = Language.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 8 | public class Language extends NamedEntity { 9 | public Language(@JsonProperty("name") String name) { 10 | super(name); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/NamedEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import org.optaplanner.core.api.domain.lookup.PlanningId; 4 | 5 | public class NamedEntity { 6 | private final String name; 7 | 8 | public NamedEntity(String name) { 9 | this.name = name; 10 | } 11 | 12 | @PlanningId 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | 22 | NamedEntity that = (NamedEntity) o; 23 | 24 | return name.equals(that.name); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return name.hashCode(); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return getClass().getSimpleName() + "{" + 35 | "name='" + name + '\'' + 36 | '}'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/ObjectMapperFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.DeserializationFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 8 | import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; 9 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 10 | 11 | import java.util.TimeZone; 12 | 13 | public class ObjectMapperFactory { 14 | private final static ObjectMapper INSTANCE; 15 | 16 | static { 17 | YAMLFactory yamlFactory = new YAMLFactory(); 18 | yamlFactory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); 19 | yamlFactory.disable(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID); 20 | ObjectMapper mapper = new ObjectMapper(yamlFactory); 21 | mapper.setTimeZone(TimeZone.getDefault()); 22 | mapper.enable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); 23 | mapper.registerModule(new JavaTimeModule()); 24 | mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); 25 | mapper.enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED); 26 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 27 | mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 28 | INSTANCE = mapper; 29 | } 30 | 31 | 32 | public static ObjectMapper getInstance() { 33 | return INSTANCE; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Room.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 8 | 9 | import java.util.Comparator; 10 | import java.util.List; 11 | 12 | @JsonIdentityInfo(scope = Room.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 13 | public class Room extends NamedEntity implements Comparable { 14 | private final int capacity; 15 | @JsonIdentityReference(alwaysAsId = true) 16 | private List availableTimeslots; 17 | @JsonIdentityReference(alwaysAsId = true) 18 | private List unavailableTimeslots; 19 | @JsonProperty(defaultValue = "false") 20 | private boolean inviteOnly; 21 | 22 | public Room(@JsonProperty("name") String name, @JsonProperty("capacity") int capacity, 23 | @JsonProperty("inviteOnly") boolean inviteOnly) { 24 | super(name); 25 | // this.name = name; 26 | this.capacity = capacity; 27 | this.inviteOnly = inviteOnly; 28 | } 29 | 30 | public int getCapacity() { 31 | return capacity; 32 | } 33 | 34 | public List getAvailableTimeslots() { 35 | return availableTimeslots; 36 | } 37 | 38 | public void setAvailableTimeslots(List availableTimeslots) { 39 | this.availableTimeslots = availableTimeslots; 40 | } 41 | 42 | public List getUnavailableTimeslots() { 43 | return unavailableTimeslots; 44 | } 45 | 46 | public void setUnavailableTimeslots(List unavailableTimeslots) { 47 | this.unavailableTimeslots = unavailableTimeslots; 48 | } 49 | 50 | @JsonIgnore 51 | public boolean isInviteOnly() { 52 | return inviteOnly; 53 | } 54 | 55 | public void setInviteOnly(boolean inviteOnly) { 56 | this.inviteOnly = inviteOnly; 57 | } 58 | 59 | @Override 60 | public int compareTo(Room o) { 61 | return getName().compareTo(o.getName()); 62 | } 63 | 64 | public static class RoomByCapacity implements Comparator { 65 | @Override 66 | public int compare(Room o1, Room o2) { 67 | return Integer.compare(o1.capacity, o2.capacity); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/RoomTimeslot.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import org.optaplanner.core.api.domain.lookup.PlanningId; 4 | 5 | import java.util.Comparator; 6 | 7 | public class RoomTimeslot { 8 | public static class RoomByCapacity implements Comparator { 9 | @Override 10 | public int compare(RoomTimeslot o1, RoomTimeslot o2) { 11 | return Integer.compare(o1.getRoom().getCapacity(), o2.getRoom().getCapacity()); 12 | } 13 | } 14 | 15 | private final Room room; 16 | private final Timeslot slot; 17 | private final String id; 18 | 19 | public RoomTimeslot(Room room, Timeslot slot) { 20 | this.room = room; 21 | this.slot = slot; 22 | this.id = room.getName() + "/" + slot.getName(); 23 | } 24 | 25 | public Room getRoom() { 26 | return room; 27 | } 28 | 29 | public Timeslot getSlot() { 30 | return slot; 31 | } 32 | 33 | @PlanningId 34 | public String getId() { 35 | return id; 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | 43 | RoomTimeslot that = (RoomTimeslot) o; 44 | 45 | if (!room.equals(that.room)) return false; 46 | return slot.equals(that.slot); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = room.hashCode(); 52 | result = 31 * result + slot.hashCode(); 53 | return result; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "RoomTimeslot{" + 59 | "room=" + room + 60 | ", slot=" + slot + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/RoomsTimeslots.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAlias; 4 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | import java.util.List; 8 | 9 | public class RoomsTimeslots { 10 | private final List rooms; 11 | @JsonAlias("timeslot") 12 | @JsonIdentityReference(alwaysAsId = true) 13 | private final List timeslots; 14 | 15 | public RoomsTimeslots(@JsonProperty("rooms") List rooms, @JsonProperty("timeslots") List timeslots) { 16 | this.rooms = rooms; 17 | this.timeslots = timeslots; 18 | } 19 | 20 | public List getRooms() { 21 | return rooms; 22 | } 23 | 24 | public List getTimeslots() { 25 | return timeslots; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | 33 | RoomsTimeslots that = (RoomsTimeslots) o; 34 | 35 | if (!rooms.equals(that.rooms)) return false; 36 | return timeslots.equals(that.timeslots); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | int result = rooms.hashCode(); 42 | result = 31 * result + timeslots.hashCode(); 43 | return result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/SpaceCleaner.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class SpaceCleaner { 6 | private static Pattern space = Pattern.compile("[\\p{Zs}\\s]+"); 7 | 8 | public String clean(String in) { 9 | return space.matcher(in).replaceAll(" "); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Speaker.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 6 | 7 | import java.time.OffsetDateTime; 8 | 9 | @JsonIdentityInfo(scope = Speaker.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 10 | public class Speaker extends NamedEntity implements Comparable { 11 | private Integer maxRoomSize; 12 | private Integer minRoomSize; 13 | private OffsetDateTime arriveTime; 14 | private OffsetDateTime leaveTime; 15 | 16 | public Speaker(@JsonProperty("name") String name) { 17 | super(name); 18 | } 19 | 20 | public OffsetDateTime getLeaveTime() { 21 | return leaveTime; 22 | } 23 | 24 | public void setLeaveTime(OffsetDateTime leaveTime) { 25 | this.leaveTime = leaveTime; 26 | } 27 | 28 | public OffsetDateTime getArriveTime() { 29 | return arriveTime; 30 | } 31 | 32 | public void setArriveTime(OffsetDateTime arriveTime) { 33 | this.arriveTime = arriveTime; 34 | } 35 | 36 | public Integer getMaxRoomSize() { 37 | return maxRoomSize; 38 | } 39 | 40 | public void setMaxRoomSize(Integer maxRoomSize) { 41 | this.maxRoomSize = maxRoomSize; 42 | } 43 | 44 | public Integer getMinRoomSize() { 45 | return minRoomSize; 46 | } 47 | 48 | public void setMinRoomSize(Integer minRoomSize) { 49 | this.minRoomSize = minRoomSize; 50 | } 51 | 52 | @Override 53 | public int compareTo(Speaker o) { 54 | return getName().compareTo(o.getName()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Talk.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import java.time.OffsetDateTime; 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | @JsonIdentityInfo(scope = Talk.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 11 | @JsonPropertyOrder({"name", "language", "speakers"}) 12 | public class Talk extends NamedEntity { 13 | @JsonAlias("speaker") 14 | @JsonIdentityReference(alwaysAsId = true) 15 | private List speakers; 16 | 17 | @JsonIdentityReference(alwaysAsId = true) 18 | private Language language; 19 | 20 | @JsonAlias("topic") 21 | @JsonIdentityReference(alwaysAsId = true) 22 | private List topics; 23 | 24 | private OffsetDateTime arriveTime; 25 | private OffsetDateTime leaveTime; 26 | private Integer maxRoomSize; 27 | private Integer minRoomSize; 28 | @JsonProperty 29 | private int numListeners; 30 | private String complexity; 31 | private boolean ignore; 32 | @JsonProperty 33 | private Room room; 34 | 35 | public Talk(@JsonProperty("language") Language language, @JsonProperty("name") String name, 36 | @JsonProperty("room") Room room) { 37 | super(name); 38 | this.language = language; 39 | this.room = room; 40 | } 41 | 42 | @JsonIgnore 43 | public String getFullTitle() { 44 | StringBuilder sb = new StringBuilder(); 45 | for (Speaker speaker : speakers) { 46 | if (sb.length() != 0) { 47 | sb.append(", "); 48 | } 49 | sb.append(speaker.getName()); 50 | } 51 | sb.append(" - "); 52 | sb.append(getName()); 53 | return sb.toString(); 54 | } 55 | 56 | public Language getLanguage() { 57 | return language; 58 | } 59 | 60 | public List getSpeakers() { 61 | return speakers; 62 | } 63 | 64 | public void setSpeakers(List speakers) { 65 | this.speakers = speakers; 66 | if (arriveTime == null) { 67 | this.arriveTime = speakers.stream().map(Speaker::getArriveTime).filter(Objects::nonNull).max(Comparator.naturalOrder()).orElse(null); 68 | } 69 | if (leaveTime == null) { 70 | this.leaveTime = speakers.stream().map(Speaker::getLeaveTime).filter(Objects::nonNull).min(Comparator.naturalOrder()).orElse(null); 71 | } 72 | if (maxRoomSize == null) { 73 | this.maxRoomSize = speakers.stream().map(Speaker::getMaxRoomSize).filter(Objects::nonNull).min(Comparator.naturalOrder()).orElse(null); 74 | } 75 | if (minRoomSize == null) { 76 | this.minRoomSize = speakers.stream().map(Speaker::getMinRoomSize).filter(Objects::nonNull).max(Comparator.naturalOrder()).orElse(null); 77 | } 78 | } 79 | 80 | public List getTopics() { 81 | return topics; 82 | } 83 | 84 | public void setTopics(List topics) { 85 | this.topics = topics; 86 | } 87 | 88 | @JsonProperty 89 | public void setArriveTime(OffsetDateTime arriveTime) { 90 | this.arriveTime = arriveTime; 91 | } 92 | 93 | @JsonProperty 94 | public void setLeaveTime(OffsetDateTime leaveTime) { 95 | this.leaveTime = leaveTime; 96 | } 97 | 98 | @JsonProperty 99 | public void setMaxRoomSize(Integer maxRoomSize) { 100 | this.maxRoomSize = maxRoomSize; 101 | } 102 | 103 | @JsonProperty 104 | public void setMinRoomSize(Integer minRoomSize) { 105 | this.minRoomSize = minRoomSize; 106 | } 107 | 108 | @JsonIgnore 109 | public OffsetDateTime getArriveTime() { 110 | return arriveTime; 111 | } 112 | 113 | @JsonIgnore 114 | public OffsetDateTime getLeaveTime() { 115 | return leaveTime; 116 | } 117 | 118 | @JsonIgnore 119 | public Integer getMaxRoomSize() { 120 | return maxRoomSize; 121 | } 122 | 123 | @JsonIgnore 124 | public Integer getMinRoomSize() { 125 | return minRoomSize; 126 | } 127 | 128 | @JsonIgnore 129 | public Room getRoom() { 130 | return room; 131 | } 132 | 133 | public void setRoom(Room room) { 134 | this.room = room; 135 | } 136 | 137 | @JsonIgnore 138 | public int getNumListeners() { 139 | return numListeners == 0 ? 30 : numListeners; 140 | } 141 | 142 | @JsonProperty 143 | public void setNumListeners(int numListeners) { 144 | this.numListeners = numListeners; 145 | } 146 | 147 | public String getComplexity() { 148 | return complexity; 149 | } 150 | 151 | public void setComplexity(String complexity) { 152 | this.complexity = complexity; 153 | } 154 | 155 | @JsonIgnore 156 | public boolean isIgnore() { 157 | return ignore; 158 | } 159 | 160 | public void setIgnore(boolean ignore) { 161 | this.ignore = ignore; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/TalkPlace.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class TalkPlace { 7 | @JsonIdentityReference(alwaysAsId = true) 8 | private final Talk talk; 9 | @JsonIdentityReference(alwaysAsId = true) 10 | private final Timeslot slot; 11 | @JsonIdentityReference(alwaysAsId = true) 12 | private final Room room; 13 | 14 | public TalkPlace(@JsonProperty("talk") Talk talk, @JsonProperty("slot") Timeslot slot, @JsonProperty("room") Room room) { 15 | this.talk = talk; 16 | this.slot = slot; 17 | this.room = room; 18 | } 19 | 20 | public Talk getTalk() { 21 | return talk; 22 | } 23 | 24 | public Timeslot getSlot() { 25 | return slot; 26 | } 27 | 28 | public Room getRoom() { 29 | return room; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "TalkPlace{" + 35 | "talk=" + talk + 36 | ", slot=" + slot + 37 | ", room=" + room + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/TalkSequence.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class TalkSequence { 7 | @JsonIdentityReference(alwaysAsId = true) 8 | private final Talk first; 9 | @JsonIdentityReference(alwaysAsId = true) 10 | private final Talk second; 11 | @JsonProperty(defaultValue = "ADJACENT") 12 | private final Type type; 13 | public TalkSequence(@JsonProperty("first") Talk first, @JsonProperty("second") Talk second 14 | , @JsonProperty("type") Type type) { 15 | this.first = first; 16 | this.second = second; 17 | this.type = type; 18 | } 19 | 20 | public Talk getFirst() { 21 | return first; 22 | } 23 | 24 | public Talk getSecond() { 25 | return second; 26 | } 27 | 28 | public Type getType() { 29 | return type; 30 | } 31 | 32 | public enum Type { 33 | ADJACENT, 34 | SOFT, 35 | CONFLICTS, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Timeslot.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | 5 | import java.time.Duration; 6 | import java.time.LocalTime; 7 | import java.time.OffsetDateTime; 8 | import java.time.ZoneOffset; 9 | import java.time.temporal.ChronoUnit; 10 | import java.util.Comparator; 11 | 12 | @JsonIdentityInfo(scope = Timeslot.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 13 | @JsonPropertyOrder({"name", "day", "start", "duration"}) 14 | public class Timeslot extends NamedEntity implements Comparable { 15 | private final static Comparator CMP = Comparator.comparing(Timeslot::getDay) 16 | .thenComparing(Timeslot::getEnd) 17 | .thenComparing(Timeslot::getStart); 18 | @JsonProperty 19 | @JsonFormat(pattern = "HH:mm") 20 | private final LocalTime start; 21 | private int duration; 22 | @JsonIdentityReference(alwaysAsId = true) 23 | private Day day; 24 | 25 | public Timeslot(@JsonProperty("name") String name 26 | , @JsonProperty("day") Day day 27 | , @JsonProperty("start") LocalTime start 28 | , @JsonProperty("duration") int duration) { 29 | super(name); 30 | this.day = day; 31 | this.start = start; 32 | this.duration = duration; 33 | } 34 | 35 | public Day getDay() { 36 | return day; 37 | } 38 | 39 | public LocalTime getStart() { 40 | return start; 41 | } 42 | 43 | @JsonIgnore 44 | public LocalTime getEnd() { 45 | return start.plus(duration, ChronoUnit.MINUTES); 46 | } 47 | 48 | @JsonIgnore 49 | public OffsetDateTime getStartTimestamp() { 50 | return OffsetDateTime.of(day.getDate(), start, ZoneOffset.ofHours(3)); 51 | } 52 | 53 | @JsonIgnore 54 | public OffsetDateTime getEndTimestamp() { 55 | return OffsetDateTime.of(day.getDate(), start.plus(duration, ChronoUnit.MINUTES), ZoneOffset.ofHours(3)); 56 | } 57 | 58 | public Duration minus(Timeslot o) { 59 | return Duration.between(o.getEndTimestamp(), getStartTimestamp()); 60 | } 61 | 62 | public int getDuration() { 63 | return duration; 64 | } 65 | 66 | @Override 67 | public int compareTo(Timeslot o) { 68 | return CMP.compare(this, o); 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "Slot{" + day.getDate() + " " + start + 74 | '}'; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/model/Topic.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.ObjectIdGenerators; 6 | 7 | @JsonIdentityInfo(scope = Topic.class, generator = ObjectIdGenerators.PropertyGenerator.class, property = "name") 8 | public class Topic extends NamedEntity { 9 | public Topic(@JsonProperty("name") String name) { 10 | super(name); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/ComplexityScorer.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.function.Function; 6 | import java.util.stream.Collectors; 7 | 8 | public class ComplexityScorer { 9 | public static long score(List complexities) { 10 | Map map = complexities.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); 11 | return map 12 | .values().stream().mapToLong(x -> x * x).sum(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/ExpectedNumListeners.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 7 | import com.github.vlsi.confplanner.model.Talk; 8 | 9 | public class ExpectedNumListeners { 10 | // @JsonIdentityReference(alwaysAsId = true) 11 | @JsonUnwrapped 12 | private final Talk talk; 13 | private double count; 14 | 15 | @JsonCreator 16 | public ExpectedNumListeners(@JsonProperty("talk") Talk talk, @JsonProperty("count") double count) { 17 | this.talk = talk; 18 | this.count = count; 19 | } 20 | 21 | public static ExpectedNumListeners create(Talk talk) { 22 | return new ExpectedNumListeners(talk, 0); 23 | } 24 | 25 | public Talk getTalk() { 26 | return talk; 27 | } 28 | 29 | @JsonIgnore 30 | public double getCount() { 31 | return count; 32 | } 33 | 34 | public void setCount(double count) { 35 | this.count = count; 36 | } 37 | 38 | public ExpectedNumListeners inc(double count) { 39 | this.count += count; 40 | return this; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | 48 | ExpectedNumListeners that = (ExpectedNumListeners) o; 49 | 50 | return talk.equals(that.talk); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return talk.hashCode(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ExpectedNumListeners{" + 61 | "talk=" + talk + 62 | ", count=" + count + 63 | '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/LanguageDiversityScorer.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import java.util.Collection; 4 | import java.util.Set; 5 | 6 | public class LanguageDiversityScorer { 7 | public static int score(Collection langs) { 8 | int en = 0; 9 | int ru = 0; 10 | for (String lang : langs) { 11 | if ("en".equals(lang)) { 12 | en++; 13 | } 14 | if ("ru".equals(lang)) { 15 | ru++; 16 | } 17 | } 18 | if (en == 0 || ru == 0) { 19 | return -100; 20 | } 21 | return Math.min(en, ru) * 20; 22 | } 23 | 24 | public static long intersectSize(Set a, Set b) { 25 | return a.stream().filter(b::contains).count(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/ScoringParams.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | public class ScoringParams { 4 | private final int roomCapacityFactor; 5 | 6 | public ScoringParams(int roomCapacityFactor) { 7 | this.roomCapacityFactor = roomCapacityFactor; 8 | } 9 | 10 | public int getRoomCapacityFactor() { 11 | return roomCapacityFactor; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/SlotTalkListeners.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.Timeslot; 4 | 5 | public class SlotTalkListeners { 6 | private final Timeslot slot; 7 | private final double count; 8 | 9 | public SlotTalkListeners(Timeslot slot, double count) { 10 | this.slot = slot; 11 | this.count = count; 12 | } 13 | 14 | @Override 15 | public boolean equals(Object o) { 16 | if (this == o) return true; 17 | if (o == null || getClass() != o.getClass()) return false; 18 | 19 | SlotTalkListeners that = (SlotTalkListeners) o; 20 | 21 | if (Double.compare(that.count, count) != 0) return false; 22 | return slot.equals(that.slot); 23 | } 24 | 25 | public Timeslot getSlot() { 26 | return slot; 27 | } 28 | 29 | public double getCount() { 30 | return count; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result; 36 | long temp; 37 | result = slot.hashCode(); 38 | temp = Double.doubleToLongBits(count); 39 | result = 31 * result + (int) (temp ^ (temp >>> 32)); 40 | return result; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "SlotTalkListeners{" + 46 | "slot=" + slot + 47 | ", count=" + count + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkConflict.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.Talk; 4 | 5 | public class TalkConflict { 6 | private final Talk a; 7 | private final Talk b; 8 | private double count; 9 | 10 | public TalkConflict(Talk a, Talk b) { 11 | this.a = a; 12 | this.b = b; 13 | } 14 | 15 | public Talk getA() { 16 | return a; 17 | } 18 | 19 | public Talk getB() { 20 | return b; 21 | } 22 | 23 | public double getCount() { 24 | return count; 25 | } 26 | 27 | public void inc(double k) { 28 | count += k; 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) return true; 34 | if (o == null || getClass() != o.getClass()) return false; 35 | 36 | TalkConflict that = (TalkConflict) o; 37 | 38 | if (!a.equals(that.a)) return false; 39 | return b.equals(that.b); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | int result = a.hashCode(); 45 | result = 31 * result + b.hashCode(); 46 | return result; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "TalkConflict{count=" + ((int) count) + 52 | ", a=" + a + 53 | ", b=" + b + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkPlacement.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.github.vlsi.confplanner.model.ConferenceData; 5 | import com.github.vlsi.confplanner.model.Room; 6 | import com.github.vlsi.confplanner.model.RoomTimeslot; 7 | import com.github.vlsi.confplanner.model.Talk; 8 | import com.github.vlsi.confplanner.model.Timeslot; 9 | import org.optaplanner.core.api.domain.entity.PlanningEntity; 10 | import org.optaplanner.core.api.domain.lookup.PlanningId; 11 | import org.optaplanner.core.api.domain.variable.PlanningVariable; 12 | 13 | import java.time.OffsetDateTime; 14 | 15 | @PlanningEntity(difficultyComparatorClass = TalkPlacementDifficultyComparator.class, 16 | movableEntitySelectionFilter = TalkPlacementMoveableFilter.class) 17 | public class TalkPlacement { 18 | private Talk talk; 19 | private boolean hasSequence; 20 | private boolean moveable = true; 21 | private RoomTimeslot roomTimeslot; 22 | 23 | public TalkPlacement() { 24 | // for planning 25 | } 26 | 27 | public TalkPlacement(Talk talk) { 28 | this.talk = talk; 29 | } 30 | 31 | public Talk getTalk() { 32 | return talk; 33 | } 34 | 35 | public void setTalk(Talk talk) { 36 | this.talk = talk; 37 | } 38 | 39 | @PlanningVariable(valueRangeProviderRefs = "roomTimeslots", strengthComparatorClass = RoomTimeslot.RoomByCapacity.class) 40 | public RoomTimeslot getRoomTimeslot() { 41 | return roomTimeslot; 42 | } 43 | 44 | public void setRoomTimeslot(RoomTimeslot roomTimeslot) { 45 | if (this.roomTimeslot == null) { 46 | System.out.println(talk + " => " + roomTimeslot); 47 | } 48 | this.roomTimeslot = roomTimeslot; 49 | } 50 | 51 | public Room getRoom() { 52 | if (roomTimeslot == null) { 53 | return null; 54 | } 55 | return roomTimeslot.getRoom(); 56 | } 57 | 58 | public Timeslot getSlot() { 59 | if (roomTimeslot == null) { 60 | return null; 61 | } 62 | return roomTimeslot.getSlot(); 63 | } 64 | 65 | @PlanningId 66 | public String getId() { 67 | return talk.getName(); 68 | } 69 | 70 | @JsonIgnore 71 | public OffsetDateTime getArriveTime() { 72 | return talk.getArriveTime(); 73 | } 74 | 75 | @JsonIgnore 76 | public OffsetDateTime getLeaveTime() { 77 | return talk.getLeaveTime(); 78 | } 79 | 80 | // @JsonIgnore 81 | public int getExpectedNumListeners() { 82 | return talk.getNumListeners(); 83 | } 84 | 85 | @JsonIgnore 86 | public boolean isHasSequence() { 87 | return hasSequence; 88 | } 89 | 90 | @JsonIgnore 91 | public void setHasSequence(boolean hasSequence) { 92 | this.hasSequence = hasSequence; 93 | } 94 | 95 | public boolean isMoveable() { 96 | return moveable; 97 | } 98 | 99 | public void setMoveable(boolean moveable) { 100 | this.moveable = moveable; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkPlacementDifficultyComparator.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import java.util.Comparator; 4 | 5 | 6 | public class TalkPlacementDifficultyComparator implements Comparator { 7 | private static final Comparator CMP = 8 | Comparator.comparing(TalkPlacement::isHasSequence, Comparator.naturalOrder()) 9 | .thenComparing(TalkPlacement::getArriveTime, Comparator.nullsFirst(Comparator.naturalOrder())) 10 | .thenComparing(TalkPlacement::getLeaveTime, Comparator.nullsFirst(Comparator.reverseOrder())) 11 | // .thenComparing(TalkPlacement::getMaxRoomSize, Comparator.nullsFirst(Comparator.naturalOrder())) 12 | .thenComparing(TalkPlacement::getExpectedNumListeners) 13 | .thenComparing(TalkPlacement::getId); 14 | 15 | @Override 16 | public int compare(TalkPlacement o1, TalkPlacement o2) { 17 | return CMP.compare(o1, o2); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkPlacementMoveableFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.ConferenceData; 4 | import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter; 5 | import org.optaplanner.core.impl.score.director.ScoreDirector; 6 | 7 | public class TalkPlacementMoveableFilter implements SelectionFilter { 8 | @Override 9 | public boolean accept(ScoreDirector scoreDirector, TalkPlacement selection) { 10 | return selection.isMoveable(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkSpeakerConflict.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.Talk; 4 | 5 | public class TalkSpeakerConflict { 6 | private final Talk a; 7 | private final Talk b; 8 | 9 | public TalkSpeakerConflict(Talk a, Talk b) { 10 | this.a = a; 11 | this.b = b; 12 | } 13 | 14 | public Talk getA() { 15 | return a; 16 | } 17 | 18 | public Talk getB() { 19 | return b; 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | 27 | TalkSpeakerConflict that = (TalkSpeakerConflict) o; 28 | 29 | if (!a.equals(that.a)) return false; 30 | return b.equals(that.b); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = a.hashCode(); 36 | result = 31 * result + b.hashCode(); 37 | return result; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "TalkSpeakerConflict{" + 43 | "a=" + a + 44 | ", b=" + b + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TalkTopicConflict.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.Talk; 4 | 5 | public class TalkTopicConflict { 6 | private final Talk a; 7 | private final Talk b; 8 | private final long count; 9 | 10 | public TalkTopicConflict(Talk a, Talk b) { 11 | this.a = a; 12 | this.b = b; 13 | this.count = a.getTopics().stream().filter(x -> b.getTopics().contains(x)).count(); 14 | } 15 | 16 | public Talk getA() { 17 | return a; 18 | } 19 | 20 | public Talk getB() { 21 | return b; 22 | } 23 | 24 | public long getCount() { 25 | return count; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | 33 | TalkTopicConflict that = (TalkTopicConflict) o; 34 | 35 | if (!a.equals(that.a)) return false; 36 | return b.equals(that.b); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | int result = a.hashCode(); 42 | result = 31 * result + b.hashCode(); 43 | return result; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "TalkTopicConflict{" + 49 | "a=" + a + 50 | ", b=" + b + 51 | ", count=" + count + 52 | '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/solver/TaskPlacementMoveFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | 3 | import com.github.vlsi.confplanner.model.ConferenceData; 4 | import org.optaplanner.core.impl.heuristic.selector.common.decorator.SelectionFilter; 5 | import org.optaplanner.core.impl.heuristic.selector.move.generic.SwapMove; 6 | import org.optaplanner.core.impl.score.director.ScoreDirector; 7 | 8 | import java.util.Objects; 9 | 10 | public class TaskPlacementMoveFilter implements SelectionFilter { 11 | @Override 12 | public boolean accept(ScoreDirector scoreDirector, SwapMove selection) { 13 | TalkPlacement leftEntity = (TalkPlacement) selection.getLeftEntity(); 14 | TalkPlacement rightEntity = (TalkPlacement) selection.getRightEntity(); 15 | return Objects.equals(leftEntity.getTalk().getRoom(), rightEntity.getTalk().getRoom()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/votes/FlatVoteReader.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | import com.fasterxml.jackson.databind.MappingIterator; 4 | import com.fasterxml.jackson.dataformat.csv.CsvMapper; 5 | import com.fasterxml.jackson.dataformat.csv.CsvParser; 6 | import com.github.vlsi.confplanner.model.Talk; 7 | 8 | import java.io.IOException; 9 | import java.net.URL; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.function.Supplier; 15 | import java.util.stream.Collectors; 16 | 17 | public class FlatVoteReader implements Supplier> { 18 | private final URL in; 19 | 20 | public FlatVoteReader(URL in) { 21 | this.in = in; 22 | } 23 | 24 | @Override 25 | public List get() { 26 | Map voteMap = new HashMap<>(); 27 | CsvMapper mapper = new CsvMapper(); 28 | mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY); 29 | try { 30 | VoteTitleParser p = new VoteTitleParser(); 31 | MappingIterator it = mapper.readerFor(Object[].class).readValues(in); 32 | it.next(); 33 | it.next(); 34 | while (it.hasNext()) { 35 | Object[] value = it.next(); 36 | List selectedTalks = new ArrayList<>(); 37 | for (int i = 7; i < value.length; i++) { 38 | Object o = value[i]; 39 | if (!(o instanceof String)) { 40 | continue; 41 | } 42 | String s = (String) o; 43 | if (s.isEmpty()) { 44 | continue; 45 | } 46 | if ("Да".equals(s) || "Нет".equals(s)) { 47 | continue; 48 | } 49 | Talk talk = p.parse(s); 50 | selectedTalks.add(talk); 51 | } 52 | FlatVotes key = new FlatVotes(selectedTalks); 53 | if (voteMap.containsKey(key)) { 54 | key = voteMap.get(key); 55 | key.setCount(key.getCount() + 1); 56 | } else { 57 | voteMap.put(key, key); 58 | } 59 | } 60 | } catch (IOException e) { 61 | throw new IllegalStateException("Unable to parse " + in); 62 | } 63 | List votes = voteMap.keySet().stream() 64 | .sorted() 65 | .collect(Collectors.toList()); 66 | 67 | // List votes = new ArrayList<>(); 68 | // try (InputStream is = in; 69 | // BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { 70 | // br.readLine(); // ignore header 71 | // 72 | // String line; 73 | // line = br.readLine(); 74 | // line = br.readLine(); 75 | // Pattern topic = Pattern.compile("(?:\"[^\"]+\"|[^,]+)"); 76 | // Pattern speakerTitle = Pattern.compile("([^—-]*) [—-] (.*)"); 77 | // while ((line = br.readLine()) != null) { 78 | // Matcher matcher = topic.matcher(line); 79 | // List selectedTalks = new ArrayList<>(); 80 | // while (matcher.find()) { 81 | // String topicLine = matcher.group(); 82 | // if (topicLine.charAt(0) == '"') { 83 | // topicLine = topicLine.substring(1, topicLine.length() - 1); 84 | // } 85 | // Talk parsed = p.parse(topicLine); 86 | // selectedTalks.add(parsed); 87 | // } 88 | // votes.add(new FlatVotes(selectedTalks)); 89 | // } 90 | // 91 | // } catch (IOException e) { 92 | // e.printStackTrace(); 93 | // } 94 | return votes; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/votes/FlatVotes.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIdentityReference; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import com.github.vlsi.confplanner.model.Talk; 7 | 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @JsonPropertyOrder({"count", "talks"}) 13 | public class FlatVotes implements Comparable { 14 | private static final Comparator CMP = Comparator.comparing(FlatVotes::getCount, Comparator.reverseOrder()) 15 | .thenComparing(Comparator.comparing(fv -> fv.getTalks().size())) 16 | .thenComparing(Comparator.comparing(fv -> fv.getTalks().stream().map(Talk::getName).collect(Collectors.joining(",")))); 17 | 18 | @JsonIdentityReference(alwaysAsId = true) 19 | private final List talks; 20 | private int count; 21 | 22 | public FlatVotes(@JsonProperty("talks") List talks, @JsonProperty("count") int count) { 23 | this.talks = talks; 24 | this.count = count; 25 | talks.sort(Comparator.comparing(Talk::getName)); 26 | } 27 | 28 | public FlatVotes(List talks) { 29 | this(talks, 1); 30 | } 31 | 32 | public List getTalks() { 33 | return talks; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || getClass() != o.getClass()) return false; 40 | 41 | FlatVotes flatVotes = (FlatVotes) o; 42 | 43 | return talks != null ? talks.equals(flatVotes.talks) : flatVotes.talks == null; 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | return talks != null ? talks.hashCode() : 0; 49 | } 50 | 51 | public int getCount() { 52 | return count; 53 | } 54 | 55 | public void setCount(int count) { 56 | this.count = count; 57 | } 58 | 59 | @Override 60 | public int compareTo(FlatVotes o) { 61 | return CMP.compare(this, o); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/votes/ParseFirstSurvey.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.vlsi.confplanner.model.ObjectMapperFactory; 5 | 6 | public class ParseFirstSurvey { 7 | public void run() { 8 | ObjectMapper mapper = ObjectMapperFactory.getInstance(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/vlsi/confplanner/votes/VoteTitleParser.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | import com.github.vlsi.confplanner.model.Language; 4 | import com.github.vlsi.confplanner.model.Room; 5 | import com.github.vlsi.confplanner.model.SpaceCleaner; 6 | import com.github.vlsi.confplanner.model.Speaker; 7 | import com.github.vlsi.confplanner.model.Talk; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class VoteTitleParser { 16 | private Pattern pattern = Pattern.compile("(.*) *[—-] *(.*?)[ ]*\\[(\\d+)/(\\w+)\\](\\(.*)?"); 17 | private SpaceCleaner cleaner = new SpaceCleaner(); 18 | 19 | public Talk parse(String line) { 20 | String val = cleaner.clean(line); 21 | Matcher matcher = pattern.matcher(val); 22 | if (!matcher.matches()) { 23 | if ("Кейноут: Juergen Hoeller —Spring Framework 5.0 on JDK 8 & 9".equals(val)) { 24 | Talk talk = new Talk(new Language("en"), "Spring Framework 5.0 on JDK 8 & 9", null); 25 | talk.setSpeakers(Arrays.asList(new Speaker("Juergen Hoeller"))); 26 | return talk; 27 | } 28 | if ("Кейноут: Михаил Гельфанд — Большие данные в современной биологии".equals(val)) { 29 | Talk talk = new Talk(new Language("ru"), "Большие данные в современной биологии", null); 30 | talk.setSpeakers(Arrays.asList(new Speaker("Михаил Гельфанд"))); 31 | return talk; 32 | } 33 | if (val.contains("Приключения Сеньора Холмса и Джуниора Ватсона в мире разработки ПО")) { 34 | Talk talk = new Talk(new Language("ru"), "Приключения Сеньора Холмса и Джуниора Ватсона в мире разработки ПО", null); 35 | talk.setSpeakers(Arrays.asList(new Speaker("Барух Садогурский"), new Speaker("Евгений Борисов"))); 36 | return talk; 37 | } 38 | throw new IllegalArgumentException("Unable to parse line <<" + line + ">>"); 39 | } 40 | String title = matcher.group(1).trim(); 41 | if (title.startsWith("«") && title.endsWith("»")) { 42 | title = title.substring(1, title.length() - 1); 43 | } 44 | title = title.trim(); 45 | String speakersStr = matcher.group(2); 46 | String[] speakers = speakersStr.split(", "); 47 | List speakerList = new ArrayList<>(); 48 | if (speakers.length == 2 && speakers[0].contains(" / ")) { 49 | speakers = speakers[0].split(" / "); 50 | } else { 51 | speakers = new String[]{speakers[0]}; 52 | } 53 | for (int i = 0; i < speakers.length; i++) { 54 | String speaker = speakers[i]; 55 | speakerList.add(new Speaker(speaker)); 56 | } 57 | String complexity = matcher.group(3); 58 | String language = matcher.group(4).toLowerCase(); 59 | if (language != null && language.length() > 2) { 60 | language = language.substring(0, 2); 61 | } 62 | Talk talk = new Talk(new Language(language), title, null); 63 | talk.setSpeakers(speakerList); 64 | talk.setComplexity(complexity); 65 | return talk; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/com/github/vlsi/confplanner/fx/Error.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 |
11 | 12 | 13 | 15 | 16 |
17 | 18 | 19 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /src/main/resources/com/github/vlsi/confplanner/solver/confScheduleScoreRules.drl: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.solver; 2 | dialect "java" 3 | 4 | import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder; 5 | 6 | import com.github.vlsi.confplanner.model.Day; 7 | import com.github.vlsi.confplanner.model.Grade; 8 | import com.github.vlsi.confplanner.solver.ComplexityScorer; 9 | import com.github.vlsi.confplanner.model.Talk; 10 | import com.github.vlsi.confplanner.model.Topic; 11 | import com.github.vlsi.confplanner.solver.TalkPlacement; 12 | import com.github.vlsi.confplanner.model.TalkSequence; 13 | import com.github.vlsi.confplanner.model.Room; 14 | import com.github.vlsi.confplanner.model.RoomTimeslot; 15 | import com.github.vlsi.confplanner.model.Timeslot; 16 | import com.github.vlsi.confplanner.solver.ExpectedNumListeners; 17 | import java.util.Collections 18 | import java.time.Duration; 19 | 20 | global HardSoftScoreHolder scoreHolder; 21 | 22 | rule "talkSpeakerIntersection" 23 | salience 1 24 | when 25 | $a: Talk($leftName: name, $leftSpeakers: speakers) 26 | $b: Talk(name > $leftName, eval(!Collections.disjoint(speakers, $leftSpeakers))) 27 | then 28 | insertLogical(new TalkSpeakerConflict($a, $b)); 29 | insertLogical(new TalkSpeakerConflict($b, $a)); 30 | end 31 | 32 | rule "talkTopicIntersection" 33 | salience 1 34 | when 35 | $a: Talk($leftName: name, topics != null, $leftTopics: topics) 36 | $b: Talk(name > $leftName, topics != null, eval(!Collections.disjoint(topics, $leftTopics))) 37 | then 38 | insertLogical(new TalkTopicConflict($a, $b)); 39 | insertLogical(new TalkTopicConflict($b, $a)); 40 | end 41 | 42 | // All the available rooms are calculated in com.github.vlsi.confplanner.model.ConferenceData.getAllRooms 43 | // so no need for a separate hard constraint for that 44 | //rule "roomIsAvailable" 45 | // when 46 | // TalkPlacement(slot != null, $slot: slot, room != null, $room: room) 47 | // not RoomTimeslot(slot == $slot, room == $room) 48 | // then 49 | // scoreHolder.addHardConstraintMatch(kcontext, -1); 50 | //end 51 | 52 | rule "oneTalkPerRoom" 53 | when 54 | TalkPlacement(roomTimeslot != null, $rts: roomTimeslot, $leftId: id, $leftTalk: talk) 55 | TalkPlacement(roomTimeslot == $rts, id > $leftId, $rightTalk: talk) 56 | // TalkPlacement(slot != null, $slot: slot, room != null, $room: room, $leftId: id) 57 | // exists TalkPlacement(id > $leftId, slot == $slot, room == $room) 58 | then 59 | scoreHolder.addHardConstraintMatch(kcontext, -1); 60 | end 61 | 62 | rule "speakerOccupancy" 63 | when 64 | TalkPlacement(slot != null, $leftTimeslot : slot, $talk: talk) 65 | TalkSpeakerConflict(a == $talk, $b: b) 66 | exists TalkPlacement(slot == $leftTimeslot, talk == $b) 67 | then 68 | scoreHolder.addHardConstraintMatch(kcontext, -50); 69 | end 70 | 71 | rule "speakerInDiscussionZone" 72 | when 73 | TalkPlacement($leftId : id, slot != null, $leftTimeslot : slot, $talk: talk) 74 | not TalkSequence(first == $talk, type == TalkSequence.Type.ADJACENT) 75 | TalkSpeakerConflict(a == $talk, $b: b) 76 | exists TalkPlacement(slot > $leftTimeslot, talk == $b 77 | , eval(Duration.between($leftTimeslot.getEndTimestamp(), slot.getStartTimestamp()).toMinutes() < 50) 78 | ) 79 | then 80 | scoreHolder.addSoftConstraintMatch(kcontext, -100); 81 | // scoreHolder.addHardConstraintMatch(kcontext, -1); 82 | end 83 | 84 | rule "multislotShareRoom" 85 | when 86 | TalkSequence($a: first, $b: second, type == TalkSequence.Type.ADJACENT) 87 | TalkPlacement(talk == $a, $ra: room, room != null) 88 | TalkPlacement(talk == $b, room != $ra, room != null) 89 | then 90 | scoreHolder.addHardConstraintMatch(kcontext, -1); 91 | end 92 | 93 | rule "multislotAreAdjacent" 94 | when 95 | TalkSequence($a: first, $b: second, type == TalkSequence.Type.ADJACENT) 96 | TalkPlacement(talk == $a, $sa: slot, slot != null) 97 | TalkPlacement(talk == $b, $sb: slot, slot != null) 98 | then 99 | int minutes = (int) $sb.minus($sa).toMinutes(); 100 | if (minutes <= 0) { 101 | // Hard constraint if "prev" talk starts later or at the same time 102 | scoreHolder.addHardConstraintMatch(kcontext, minutes); 103 | // } 104 | } else { 105 | if (minutes > 90) { 106 | scoreHolder.addHardConstraintMatch(kcontext, -minutes); 107 | } else { 108 | // Soft constraint to use closer slots 109 | scoreHolder.addSoftConstraintMatch(kcontext, -(minutes-30)*$a.getNumListeners()/10); 110 | } 111 | } 112 | end 113 | 114 | rule "dependentTalks" 115 | when 116 | TalkSequence($a: first, $b: second, type == TalkSequence.Type.SOFT) 117 | TalkPlacement(talk == $a, $sa: slot, slot != null) 118 | TalkPlacement(talk == $b, $sb: slot, slot != null) 119 | then 120 | int minutes = (int) $sb.minus($sa).toMinutes(); 121 | if (minutes <= 0) { 122 | // Hard constraint if "prev" talk starts later or at the same time 123 | scoreHolder.addHardConstraintMatch(kcontext, minutes); 124 | } 125 | end 126 | 127 | rule "conflictingTalksInSlot" 128 | when 129 | TalkSequence($a: first, $b: second, type == TalkSequence.Type.CONFLICTS) 130 | TalkPlacement(talk == $a, $sa: slot, slot != null) 131 | TalkPlacement(talk == $b, slot == $sa) 132 | then 133 | scoreHolder.addHardConstraintMatch(kcontext, -1); 134 | end 135 | 136 | rule "talkShouldBeInSpecialRoom" 137 | when 138 | $t: Talk(room != null, $r: room) 139 | TalkPlacement(room != null, talk == $t, room != $r, $tpr: room) 140 | then 141 | // System.out.println("talk " + $t + " should be in room " + $r + ", actual room is " + $tpr); 142 | scoreHolder.addHardConstraintMatch(kcontext, -1); 143 | end 144 | 145 | rule "talkShouldBeInRegularRoom" 146 | when 147 | $r: Room(inviteOnly) 148 | $t: Talk(room == null) 149 | TalkPlacement(talk == $t, room == $r) 150 | then 151 | scoreHolder.addHardConstraintMatch(kcontext, -1); 152 | end 153 | 154 | //rule "multislotAreAdjacent" 155 | // when 156 | // Talk(prevTitle != null, $prevTitle: prevTitle, $room: room, slot != null, $slot: slot, $cnt: numListeners) 157 | // Talk(title == $prevTitle, slot!=null, $prevSlot: slot) 158 | // then 159 | // int minutes = (int) $slot.minus($prevSlot).toMinutes(); 160 | //// System.out.println("!!!" + $prevSlot + " " + $slot + ": " + minutes); 161 | // if (minutes <= 0) { 162 | // // Hard constraint if "prev" talk starts later or at the same time 163 | // scoreHolder.addHardConstraintMatch(kcontext, minutes); 164 | //// } 165 | // } else { 166 | //// Soft constraint to use closer slots 167 | // scoreHolder.addSoftConstraintMatch(kcontext, -(minutes-30)*$cnt/10); 168 | // } 169 | //end 170 | // 171 | //rule "multislotShouldBeBefore1500" 172 | // when 173 | // Talk(prevTitle != null, slot != null, slot.start.hour >= 15) 174 | // then 175 | // scoreHolder.addHardConstraintMatch(kcontext, -1); 176 | //end 177 | 178 | rule "speakerBannedFromBigRoom" 179 | when 180 | TalkPlacement(talk.maxRoomSize != null, $maxSize: talk.maxRoomSize, room != null, $room: room, $room.capacity > $maxSize) 181 | then 182 | scoreHolder.addHardConstraintMatch(kcontext, $maxSize - $room.getCapacity()); 183 | end 184 | 185 | rule "speakerRequiresBigRoom" 186 | when 187 | TalkPlacement(talk.minRoomSize != null, $minSize: talk.minRoomSize, room != null, $room: room, $room.capacity < $minSize) 188 | then 189 | scoreHolder.addHardConstraintMatch(kcontext, $room.getCapacity() - $minSize); 190 | end 191 | 192 | // Soft constraints 193 | rule "roomCapacity" 194 | when 195 | ScoringParams($roomCapacityFactor: roomCapacityFactor) 196 | $room: Room($capacity: capacity) 197 | TalkPlacement(room == $room, $talk: talk, $count: expectedNumListeners) 198 | then 199 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(-Math.abs(0.8*$capacity-$expCnt)*0.5)); 200 | // double fill = 1.0*$count/$capacity; 201 | // double k = 1;//fill > 1 ? 2: (fill<0.9?0.5:0.9); 202 | // int score = (int)(-Math.abs(Math.atan(fill-1)*k*$capacity)); 203 | // System.out.println("$room = " + $room+ ", "+$count+", " + score); 204 | int score = (int)Math.round(Math.pow($count-$capacity, 2)/10000*$roomCapacityFactor); 205 | if ($count > $capacity) { 206 | score += $count-$capacity; 207 | } 208 | scoreHolder.addSoftConstraintMatch(kcontext, -score); 209 | 210 | // if (fill < 0.6) { 211 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(-(0.9*$capacity-$expCnt)*2)); 212 | // } else if (fill < 0.9) { 213 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(-Math.abs($expCnt-0.9*$capacity)*0.5)); 214 | // } else if (fill > 1.3){ 215 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(-($expCnt-$capacity)*2)); 216 | // } else if (fill > 1){ 217 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(-($expCnt-$capacity))); 218 | // } 219 | // if (fill > 0.9) { 220 | // } else { 221 | // scoreHolder.addSoftConstraintMatch(kcontext, (int)(($expCnt-0.9*$capacity)*0.5)); 222 | // } 223 | // scoreHolder.addSoftConstraintMatch(kcontext, -50); 224 | end 225 | 226 | rule "perSlotCapacity" 227 | when 228 | ScoringParams($roomCapacityFactor: roomCapacityFactor) 229 | $t: Timeslot() 230 | accumulate(TalkPlacement(slot == $t, $cnt: expectedNumListeners) 231 | , $score: sum($cnt)) 232 | then 233 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)Math.round($score*$score/1000.0/100/4*$roomCapacityFactor)); 234 | end 235 | 236 | rule "topicDiversity" 237 | when 238 | TalkPlacement($leftId : id, $leftTalk : talk, $leftTimeslot : slot) 239 | TalkTopicConflict(a == $leftTalk, $b: b, $count: count) 240 | TalkPlacement(slot == $leftTimeslot, id > $leftId, talk == $b) 241 | then 242 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)(50*$count)); 243 | end 244 | 245 | rule "topicPerDayDiversity" 246 | when 247 | $d: Day() 248 | $topic: Topic() 249 | accumulate($timeslot: Timeslot(day == $d) 250 | and TalkPlacement(slot == $timeslot, $talk: talk, eval($talk.getTopics().contains($topic))) 251 | , $score: count(1)) 252 | then 253 | // System.out.println($d +", "+$topic+": " + $score); 254 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)(7*$score*$score)); 255 | end 256 | 257 | rule "visitMostTalks" 258 | when 259 | TalkConflict($a: a, $b: b, $count: count) 260 | TalkPlacement(talk == $a, $slot: slot, $ra: room) 261 | TalkPlacement(talk == $b, slot == $slot, $rb: room) 262 | then 263 | // number of people that will miss second talk 264 | double score = $count; 265 | int oba = $ra == null ? 0 : ($a.getNumListeners() - $ra.getCapacity()); 266 | int obb = $rb == null ? 0 : ($b.getNumListeners() - $rb.getCapacity()); 267 | if (oba != 0 && obb!= 0) { 268 | score *= 0.5; 269 | } 270 | // if (oba != 0 && obb!= 0 && (oba > 0 ^ obb>0)) { 271 | // int ob1, ob2; 272 | // int c1, c2; 273 | // if (oba > 0) { 274 | // // overbook in room A 275 | // ob1 = oba; 276 | // ob2 = obb; 277 | // c1 = $ra.getCapacity(); 278 | // c2 = $rb.getCapacity(); 279 | // } else { 280 | // ob1 = obb; 281 | // ob2 = oba; 282 | // c1 = $rb.getCapacity(); 283 | // c2 = $ra.getCapacity(); 284 | // } 285 | // double moved = Math.min($count * oba/c1, -obb); 286 | // score -= moved; 287 | // } 288 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)Math.round(score)); 289 | end 290 | 291 | rule "languageDiversity" 292 | when 293 | $slot : Timeslot() 294 | accumulate ( 295 | TalkPlacement(talk.language != null, $lang: talk.language, slot == $slot), 296 | $langs: collectList($lang.getName()) 297 | ) 298 | then 299 | scoreHolder.addSoftConstraintMatch(kcontext, LanguageDiversityScorer.score($langs)); 300 | end 301 | 302 | rule "complexityDiversity" 303 | when 304 | $slot : Timeslot() 305 | accumulate ( 306 | TalkPlacement(talk.complexity != null, $compl: talk.complexity, slot == $slot, $compl != null), 307 | $compls: collectList($compl) 308 | ) 309 | then 310 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)(4*ComplexityScorer.score($compls))); 311 | end 312 | 313 | rule "complexityPerDayDiversity" 314 | when 315 | $d: Day() 316 | accumulate($timeslot: Timeslot(day == $d) 317 | and TalkPlacement(slot == $timeslot, $talk: talk, $compl: talk.complexity, $compl != null) 318 | , $compls: collectList($compl) 319 | ) 320 | then 321 | 322 | // System.out.println($d +", "+$topic+": " + $score); 323 | scoreHolder.addSoftConstraintMatch(kcontext, -(int)(6*ComplexityScorer.score($compls))); 324 | end 325 | 326 | rule "nextTalkSharesTopic" 327 | when 328 | TalkPlacement(slot != null, $leftTalk: talk, $leftTimeslot : slot, $prevRoom: room) 329 | TalkTopicConflict(a == $leftTalk, $b: b, $count: count) 330 | TalkPlacement(slot != null, slot > $leftTimeslot, $slot : slot, talk==$b 331 | , room == $prevRoom) 332 | eval($slot.minus($leftTimeslot).toMinutes() < 60) 333 | then 334 | scoreHolder.addSoftConstraintMatch(kcontext, 10); 335 | end 336 | 337 | 338 | rule "speakerLeavingEarly" 339 | when 340 | TalkPlacement(talk.leaveTime != null, slot != null, $leaveTime: talk.leaveTime, $slot: slot, $slot.endTimestamp > $leaveTime) 341 | then 342 | scoreHolder.addHardConstraintMatch(kcontext, -(int)(Duration.between($leaveTime, $slot.getEndTimestamp()).toMinutes())); 343 | end 344 | 345 | rule "speakerArrivingLate" 346 | when 347 | TalkPlacement(talk.arriveTime != null, slot != null, $arriveTime: talk.arriveTime, $slot: slot, $slot.startTimestamp < $arriveTime) 348 | then 349 | scoreHolder.addHardConstraintMatch(kcontext, -(int)(Duration.between($slot.getStartTimestamp(), $arriveTime).toMinutes())); 350 | end -------------------------------------------------------------------------------- /src/main/resources/com/github/vlsi/confplanner/solver/confScheduleSolverConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.github.vlsi.confplanner.model.ConferenceData 5 | com.github.vlsi.confplanner.solver.TalkPlacement 6 | 7 | 8 | com/github/vlsi/confplanner/solver/confScheduleScoreRules.drl 9 | 10 | 11 | 12 | 2 13 | 14 | 15 | 16 | FIRST_FIT_DECREASING 17 | 18 | 19 | 20 | 21 | 22 | com.github.vlsi.confplanner.solver.TaskPlacementMoveFilter 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 7000 33 | 0.05 34 | 35 | 36 | 37 | 4 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | target/run.log 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | %d %p %C{1.} [%t] %m%n 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/java/com/github/vlsi/confplanner/votes/FlatVoteReaderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | public class FlatVoteReaderTest { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/com/github/vlsi/confplanner/votes/VoteTitleParserTest.java: -------------------------------------------------------------------------------- 1 | package com.github.vlsi.confplanner.votes; 2 | 3 | import com.github.vlsi.confplanner.model.Language; 4 | import com.github.vlsi.confplanner.model.Speaker; 5 | import com.github.vlsi.confplanner.model.Talk; 6 | import org.testng.Assert; 7 | import org.testng.annotations.Test; 8 | 9 | import java.util.Arrays; 10 | 11 | public class VoteTitleParserTest { 12 | @Test 13 | public void testSingleSpeaker() throws Exception { 14 | VoteTitleParser p = new VoteTitleParser(); 15 | Talk talk = p.parse("От клика к прогнозу и обратно: Data Science пайплайны в ОК — Дмитрий Бугайченко, Одноклассники [300/RU]"); 16 | Assert.assertEquals(talk.getLanguage(), new Language("ru")); 17 | Assert.assertEquals(talk.getName(), "От клика к прогнозу и обратно: Data Science пайплайны в ОК"); 18 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Дмитрий Бугайченко"))); 19 | } 20 | 21 | @Test 22 | public void testSingleSpeaker2() throws Exception { 23 | VoteTitleParser p = new VoteTitleParser(); 24 | Talk talk = p.parse("Глубокое обучение, вероятностное программирование и метавычисления: точка пересечения — Алексей Потапов, Университет ИТМО [400/RU]"); 25 | Assert.assertEquals(talk.getLanguage(), new Language("ru")); 26 | Assert.assertEquals(talk.getName(), "Глубокое обучение, вероятностное программирование и метавычисления: точка пересечения"); 27 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Алексей Потапов"))); 28 | } 29 | 30 | @Test 31 | public void testSingleSpeaker3() throws Exception { 32 | VoteTitleParser p = new VoteTitleParser(); 33 | Talk talk = p.parse("«Жизнь без подключения: от хаоса к консенсусу» - Евгений Камышанов, EPAM Systems [300/RU]"); 34 | Assert.assertEquals(talk.getLanguage(), new Language("ru")); 35 | Assert.assertEquals(talk.getName(), "Жизнь без подключения: от хаоса к консенсусу"); 36 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Евгений Камышанов"))); 37 | } 38 | 39 | @Test 40 | public void testMultiSpeaker() throws Exception { 41 | VoteTitleParser p = new VoteTitleParser(); 42 | Talk talk = p.parse("Troubleshooting & debugging production applications in Kubernetes (a.k.a. The Failing Demo Talk) — Ray Тsang, Baruch Sadogursky, Google, JFrog  [200/EN]"); 43 | Assert.assertEquals(talk.getLanguage(), new Language("en")); 44 | Assert.assertEquals(talk.getName(), "Troubleshooting & debugging production applications in Kubernetes (a.k.a. The Failing Demo Talk)"); 45 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Ray Тsang"), new Speaker("Baruch Sadogursky"))); 46 | } 47 | 48 | @Test 49 | public void testKeynote() throws Exception { 50 | VoteTitleParser p = new VoteTitleParser(); 51 | Talk talk = p.parse("Кейноут: Unchain my heart —Dino Esposito  [200/ENG]"); 52 | Assert.assertEquals(talk.getLanguage(), new Language("en")); 53 | Assert.assertEquals(talk.getName(), "Кейноут: Unchain my heart"); 54 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Dino Esposito"))); 55 | } 56 | 57 | @Test 58 | public void testComment() throws Exception { 59 | VoteTitleParser p = new VoteTitleParser(); 60 | Talk talk = p.parse("The (Ab)use and misuse of test automation — Alan Page, Unity [300/EN](автор книги про тесты в Microsoft)"); 61 | Assert.assertEquals(talk.getLanguage(), new Language("en")); 62 | Assert.assertEquals(talk.getName(), "The (Ab)use and misuse of test automation"); 63 | Assert.assertEquals(talk.getSpeakers(), Arrays.asList(new Speaker("Alan Page"))); 64 | } 65 | } 66 | --------------------------------------------------------------------------------