Initial commit
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Project exclude paths
|
||||
/.gradle/
|
||||
/.idea/
|
||||
**/build/
|
||||
!src/**/build/
|
||||
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2020] [Siniša Sovilj]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
223
README.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Kotlin System Dynamics Toolkit
|
||||
|
||||
**Kotlin SD Toolkit** or *ksdtookit* is a tool suitable for both:
|
||||
- modelling larger, hierarchical **system dynamics** (SD) models
|
||||
(supporting modules) and
|
||||
- for automatic generation of **interactive simulators** for multiple
|
||||
target platforms: *desktop*, *web* or *mobile* using Kotlin programming language.
|
||||
|
||||
## Intro
|
||||
|
||||
*Kotlin* is a new (first appeared in 2011), statically-typed programming
|
||||
language, with:
|
||||
- modern, more expressive syntax,
|
||||
- null-pointer exception safety,
|
||||
- both object-oriented and functional programming capabilities,
|
||||
- interoperable with all existing Java libraries and frameworks, and
|
||||
- since 2017 Google’s preferred language for Android application development;
|
||||
|
||||
*Kotlin* also excels in developing internal, **domain-specific language**
|
||||
(internal DSL), which allows adapting general-purpose language (e.g. *Kotlin*) to solve problems of
|
||||
specific domain (e.g. system dynamics).
|
||||
|
||||
*ksdtookit* consists of three modules:
|
||||
1. *ksdtoolkit-core* - core module for SD modelling & simulation,
|
||||
with exporters (CSV, PNG), and desktop simulator app,
|
||||
2. *ksdtoolkit-mobapp* - mobile simulator app module,
|
||||
3. *ksdtoolkit-webapp* - web simulator app module.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
#### Example:
|
||||
|
||||
Modelling Innovation / Product diffusion SD model (also known as Bass diffusion model).
|
||||
|
||||

|
||||
|
||||
#### 0. Setup
|
||||
```
|
||||
// Static properties (optional)
|
||||
companion object {
|
||||
const val TOTAL_POPULATION_VALUE = 10000 // [customer]
|
||||
const val ADVERTISING_EFFECTIVENESS_VALUE = 0.011 // [1/year]
|
||||
const val CONTACT_RATE_VALUE = 100 // [1/year]
|
||||
const val ADOPTION_FRACTION_VALUE = 0.015 // []
|
||||
|
||||
const val INITIAL_TIME_VALUE = 0 // [year]
|
||||
const val FINAL_TIME_VALUE = 10 // [year]
|
||||
const val TIME_STEP_VALUE = 0.25 // [year]
|
||||
}
|
||||
```
|
||||
|
||||
#### 1. Model
|
||||
```
|
||||
init {
|
||||
val model = Model()
|
||||
|
||||
// Override default model properties
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.integration = EulerIntegration()
|
||||
model.name = "Innovation/Product Diffusion Model" // optional
|
||||
```
|
||||
|
||||
#### 2. Entities
|
||||
##### Constants
|
||||
```
|
||||
val TOTAL_POPULATION = model.constant("TOTAL_POPULATION")
|
||||
val ADVERTISING_EFFECTIVENESS = model.constant("ADVERTISING_EFFECTIVENESS")
|
||||
val CONTACT_RATE = model.constant("CONTACT_RATE")
|
||||
val ADOPTION_FRACTION = model.constant("ADOPTION_FRACTION")
|
||||
```
|
||||
|
||||
##### Converters
|
||||
```
|
||||
val adoptionFromAdvertising =
|
||||
model.converter("adoptionFromAdvertising")
|
||||
val adoptionFromWordOfMouth =
|
||||
model.converter("adoptionFromWordOfMouth")
|
||||
```
|
||||
##### Stocks
|
||||
```
|
||||
val Potential_Adopters = model.stock("Potential_Adopters")
|
||||
val Adopters = model.stock("Adopters")
|
||||
```
|
||||
##### Flows
|
||||
```
|
||||
val adoptionRate = model.flow("adoptionRate")
|
||||
```
|
||||
##### Modules
|
||||
```
|
||||
```
|
||||
|
||||
#### 3. Initial values
|
||||
##### Stocks
|
||||
```
|
||||
Potential_Adopters.initialValue = { TOTAL_POPULATION }
|
||||
Adopters.initialValue = { 0.0 }
|
||||
```
|
||||
|
||||
#### 4. Equations
|
||||
##### Constants
|
||||
```
|
||||
TOTAL_POPULATION.equation = { TOTAL_POPULATION_VALUE }
|
||||
ADVERTISING_EFFECTIVENESS.equation = { ADVERTISING_EFFECTIVENESS_VALUE }
|
||||
CONTACT_RATE.equation = { CONTACT_RATE_VALUE }
|
||||
ADOPTION_FRACTION.equation = { ADOPTION_FRACTION_VALUE }
|
||||
```
|
||||
##### Converters
|
||||
```
|
||||
adoptionFromAdvertising.equation =
|
||||
{ Potential_Adopters * ADVERTISING_EFFECTIVENESS }
|
||||
adoptionFromWordOfMouth.equation =
|
||||
{ CONTACT_RATE * ADOPTION_FRACTION *
|
||||
Potential_Adopters * Adopters / TOTAL_POPULATION }
|
||||
```
|
||||
##### Stocks
|
||||
```
|
||||
Potential_Adopters.equation = { - adoptionRate }
|
||||
Adopters.equation = { adoptionRate }
|
||||
```
|
||||
##### Flows
|
||||
```
|
||||
adoptionRate.equation =
|
||||
{ adoptionFromAdvertising + adoptionFromWordOfMouth }
|
||||
```
|
||||
##### Modules
|
||||
```
|
||||
```
|
||||
|
||||
#### 5. Simulation
|
||||
```
|
||||
val simulation = Simulation(model)
|
||||
```
|
||||
|
||||
#### 6. Outputs
|
||||
```
|
||||
simulation.outputs {
|
||||
CsvExporter("output.csv", ";")) // Text
|
||||
PngExporter("chart.png")) // Image
|
||||
WinSimulator() // Desktop
|
||||
WebSimulator() // Web
|
||||
MobSimulator() // Mobile
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Run
|
||||
```
|
||||
simulation.run()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Whole *ksdtoolkit* project and all three project modules (*ksdtoolkit-core*, *ksdtoolkit-mobapp*, *ksdtoolkit-webapp*)
|
||||
are designed as **Gradle** project/modules (using *Gradle Kotlin DSL*). Therefore, use **Gradlew Wrapper**
|
||||
(*gradlew* in Windows or *./gradlew* in Linux/Mac) for
|
||||
clean, build, test and run project/modules.
|
||||
|
||||
To run Gradle tasks, from IntelliJ one can use *Terminal window* (be in the root path where *gradlew* file is located).
|
||||
(Instead of Terminal, an alternative is to install and use Gradle Plugin in IntelliJ.)
|
||||
|
||||
To clean and build whole project (and all modules) use:
|
||||
```
|
||||
gradlew clean build
|
||||
```
|
||||
(Close PngExporter and WinSimulator windows to allow build to finish).
|
||||
|
||||
|
||||
#### Desktop simulator
|
||||
To build only *ksdtoolkit-core* module use:
|
||||
```
|
||||
gradlew :ksdtoolkit-core:build
|
||||
```
|
||||
Build will also run all unit tests located in *./ksdtoolkit-core/src/test/kotlin/hr.unipu.ksdtookit/*
|
||||
|
||||
The last unit test `5_SimulationOutputsTest` contains output tests where e.g. *WinSimulator* is launched.
|
||||
|
||||

|
||||
|
||||
|
||||
#### Web simulator
|
||||
To build *ksdtoolkit-webapp* module and run web server use:
|
||||
```
|
||||
gradlew :ksdtoolkit-webapp:appStart
|
||||
```
|
||||
The web simulator will be accessible at: ```http://localhost:8080/```
|
||||
|
||||

|
||||
(Valid developer licence numbers are needed for using *Vaadin Charts* and *Vaadin Spreadsheet*).
|
||||
|
||||
|
||||
#### Mobile simulator
|
||||
In IntelliJ first create and launch Android emulator (Tools | Android | AVD Manager).
|
||||
To build *ksdtoolkit-mobapp* module and install *apk* file on Android emulator use:
|
||||
```
|
||||
gradlew :ksdtoolkit-mobapp:appStart
|
||||
```
|
||||
The mobile simulator will be automatically launched.
|
||||
|
||||

|
||||
|
||||
|
||||
## Authors
|
||||
|
||||
- **Siniša Sovilj**<sup>1</sup> <sinisa.sovilj@unipu.hr>
|
||||
- **Darko Etinger**<sup>1</sup> <darko.etinger@unipu.hr>
|
||||
- **Krešimir Pripužić**<sup>2</sup> <kresimir.pripuzic@fer.hr>
|
||||
|
||||
<sup>1 = Juraj Dobrila University of Pula, Faculty of Informatics, HR-52100 Pula, CROATIA </sup> \
|
||||
<sup>2 = University of Zagreb, Faculty of Electrical Engineering and Computing, HR-10000 Zagreb, CROATIA </sup>
|
||||
|
||||
|
||||
## License
|
||||
This project is licensed under the Apache License 2.0 License. See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
|
||||
## Acknowledgments
|
||||
Kotlin SD Toolkit was inspired by the great work of:
|
||||
* Drost & Stein - [System Dynamics Java-Framework](https://github.com/matthiasstein/SystemDynamics-Framework),
|
||||
* Schroeck - [Business Prototyping Toolkit for Python](https://github.com/transentis/bptk_py_tutorial).
|
||||
22
build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Root project.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
plugins {
|
||||
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven(url="https://maven.google.com")
|
||||
maven(url="https://jitpack.io")
|
||||
}
|
||||
}
|
||||
|
||||
group = "hr.unpu"
|
||||
version = "1.0-SNAPSHOT"
|
||||
5
gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
kotlin.code.style=official
|
||||
systemProp.vaadin.charts.developer.license=c0171602-2326-457a-837d-226c949709f1
|
||||
systemProp.vaadin.spreadsheet.developer.license=8bf94b9d-b891-47b0-bad3-971445a274a5
|
||||
vaadin.charts.developer.license=c0171602-2326-457a-837d-226c949709f1
|
||||
vaadin.spreadsheet.developer.license=8bf94b9d-b891-47b0-bad3-971445a274a5
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
185
gradlew
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
gradlew.bat
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
BIN
images/Figure01.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
images/Figure02.png
Normal file
|
After Width: | Height: | Size: 301 KiB |
BIN
images/Figure03.png
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
images/Figure04.png
Normal file
|
After Width: | Height: | Size: 501 KiB |
46
ksdtoolkit-core/build.gradle.kts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Subproject: "ksdtoolkit-core".
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
|
||||
implementation("org.hamcrest:hamcrest-all:1.3")
|
||||
|
||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
implementation("ch.qos.logback:logback-core:1.2.3")
|
||||
|
||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||
}
|
||||
|
||||
val compileKotlin: KotlinCompile by tasks
|
||||
compileKotlin.kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
val compileTestKotlin: KotlinCompile by tasks
|
||||
compileTestKotlin.kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
BIN
ksdtoolkit-core/output_chart.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
482
ksdtoolkit-core/output_data.csv
Normal file
@@ -0,0 +1,482 @@
|
||||
Module.inflow;outflow;Module.CONSTANT;Module.converter;Module.outflow;Module.INITIAL_STOCK;INITIAL_STOCK;converter;CONSTANT;inflow;Stock;Module.Stock
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
0.833333;0.833333;10;0.008333;0.833333;100;100;0.008333;10;0.833333;100;100
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Constant entity.
|
||||
*
|
||||
* Constant class extends abstract class ModelEntity and only passes 'name' to the abstract class.
|
||||
*
|
||||
* @property name Constant name (only passed to the abstract class ModelEntity).
|
||||
*/
|
||||
|
||||
class Constant(name: String) : ModelEntity(name)
|
||||
@@ -0,0 +1,82 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
import java.util.*
|
||||
import kotlin.IllegalArgumentException
|
||||
|
||||
/**
|
||||
* Converter entity.
|
||||
*
|
||||
* Converter calculates a current value of a target [ModelEntity] in a time step, representing the cause-effect
|
||||
* relationship between [ModelEntity] instances. Converters takes input data and converts it into some outputs
|
||||
* signal. Converter calculations depends on the [equation] that has been delivered to the converter.
|
||||
* Details:
|
||||
* - Converters are used to disaggregate the complex functions that define flows into their constituent parts.
|
||||
* - Converters may be influenced by stocks and can influence other converters.
|
||||
* - In general, it converts [inputEntities] into an [targetEntity].
|
||||
* - E.g. Unlike flows (which are special kinds of converters), they cannot directly influence a stock.
|
||||
*
|
||||
* @param targetEntity Target (outputs) model entity (calculated value).
|
||||
* @param inputEntities Input model entities (that influence calculation).
|
||||
*
|
||||
*/
|
||||
class Converter(name: String) : ModelEntity(name) {
|
||||
|
||||
private var inputEntities = arrayListOf<ModelEntity>()
|
||||
lateinit var targetEntity: ModelEntity
|
||||
|
||||
|
||||
constructor(targetEntity: ModelEntity,
|
||||
vararg inputEntities: ModelEntity
|
||||
) : this(targetEntity.name) {
|
||||
|
||||
Collections.addAll(this.inputEntities, *inputEntities)
|
||||
this.targetEntity = targetEntity
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert the target model entity to a value.
|
||||
*/
|
||||
fun convert() {
|
||||
for (input in this.inputEntities) {
|
||||
if (!input.isCurrentValueCalculated && input.converter != null) {
|
||||
input.converter!!.convert()
|
||||
}
|
||||
}
|
||||
|
||||
targetEntity.currentValue = _equation?.invoke() ?: 0.0
|
||||
targetEntity.isCurrentValueCalculated = true
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add multiple input model entities.
|
||||
*/
|
||||
fun addInputs(vararg inputs: ModelEntity) {
|
||||
for (input in inputs) {
|
||||
this.addInput(input)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an input for the target entity.
|
||||
*/
|
||||
private fun addInput(input: ModelEntity) {
|
||||
if (!inputAlreadyAdded(input)) {
|
||||
this.inputEntities.add(input)
|
||||
} else {
|
||||
throw IllegalArgumentException("Duplicate variable exception.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a ModelEntity has been already added to the Converter.
|
||||
*/
|
||||
private fun inputAlreadyAdded(input: ModelEntity): Boolean {
|
||||
return inputEntities.contains(input)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Flow entity.
|
||||
*
|
||||
* Flow can be an "input_flow" or "output_flow", which difference defines "change_rate" of a [Stock].
|
||||
* Flows are special kind of converters. Flows can also be inputEntities to other converters.
|
||||
*
|
||||
* @property name Flow name (only passed to the abstract class ModelEntity).
|
||||
*/
|
||||
|
||||
class Flow(name: String) : ModelEntity(name)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Function interface.
|
||||
* (Not used anymore, just for legacy.)
|
||||
*/
|
||||
interface IFunction {
|
||||
fun calculateEntityValue(): Double
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.integration.Integration
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.IllegalArgumentException
|
||||
import kotlin.reflect.full.createInstance
|
||||
|
||||
/**
|
||||
* Model class represents simulation model.
|
||||
*
|
||||
* Model defines all [ModelEntity] instances and their cause-effect relationships.
|
||||
*/
|
||||
|
||||
open class Model(
|
||||
/**
|
||||
* Primary constructor.
|
||||
*/
|
||||
var initialTime: Number = 0.0,
|
||||
var finalTime: Number = 100.0,
|
||||
var timeStep: Number = 0.25,
|
||||
var integrationType: Integration = EulerIntegration(),
|
||||
var name: String = "",
|
||||
var timeUnit: String = "",
|
||||
|
||||
val entities: HashMap<String, ModelEntity> = hashMapOf(),
|
||||
val converters: ArrayList<Converter> = arrayListOf(),
|
||||
val modules: HashMap<String, Model> = hashMapOf(),
|
||||
|
||||
var currentTime: Double = initialTime.toDouble()
|
||||
|
||||
) {
|
||||
|
||||
/**
|
||||
* Getter: list of stocks.
|
||||
*/
|
||||
val stocks: List<Stock>
|
||||
get() {
|
||||
val stocks = ArrayList<Stock>()
|
||||
val modelEntities = ArrayList(this.entities.values)
|
||||
modelEntities.forEach { modelEntity ->
|
||||
if (modelEntity is Stock) {
|
||||
stocks.add(modelEntity)
|
||||
}
|
||||
}
|
||||
return stocks
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter: list of model entity keys.
|
||||
*/
|
||||
val modelEntitiesKeys: List<String>
|
||||
get() {
|
||||
return ArrayList(this.entities.keys)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter: list of model entity units.
|
||||
*/
|
||||
val modelEntitiesUnits: List<String>
|
||||
get() {
|
||||
val modelEntities = this.entities
|
||||
val modelEntityUnits = ArrayList<String>()
|
||||
modelEntities.forEach { modelEntity ->
|
||||
modelEntityUnits.add(modelEntity.value.unit)
|
||||
}
|
||||
return modelEntityUnits
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter: list of model entity values.
|
||||
*/
|
||||
val modelEntitiesValues: List<String>
|
||||
get() {
|
||||
val modelEntityValues = ArrayList<String>()
|
||||
val modelEntities = ArrayList(this.entities.values)
|
||||
modelEntities.forEach { modelEntity ->
|
||||
modelEntityValues.add(DecimalFormat("#.######").format(modelEntity.currentValue))
|
||||
}
|
||||
return modelEntityValues
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new model entity.
|
||||
*/
|
||||
fun createModelEntity(entityType: ModelEntityType, name: String): ModelEntity? {
|
||||
val modelEntity: ModelEntity
|
||||
when (entityType) {
|
||||
ModelEntityType.STOCK -> modelEntity = Stock(name)
|
||||
ModelEntityType.FLOW -> modelEntity = Flow(name)
|
||||
ModelEntityType.CONSTANT -> modelEntity = Constant(name)
|
||||
ModelEntityType.CONVERTER -> modelEntity = Converter(name)
|
||||
}
|
||||
this.addModelEntity(modelEntity)
|
||||
return modelEntity
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new entity - Stock.
|
||||
*/
|
||||
fun stock(name: String): Stock {
|
||||
val modelEntity: ModelEntity = Stock(name)
|
||||
this.addModelEntity(modelEntity)
|
||||
return modelEntity as Stock
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new entity - Flow.
|
||||
*/
|
||||
fun flow(name: String): Flow {
|
||||
val modelEntity: ModelEntity = Flow(name)
|
||||
this.addModelEntity(modelEntity)
|
||||
return modelEntity as Flow
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new entity - Constant.
|
||||
*/
|
||||
fun constant(name: String): Constant {
|
||||
val modelEntity: ModelEntity = Constant(name)
|
||||
this.addModelEntity(modelEntity)
|
||||
return modelEntity as Constant
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new entity - Converter.
|
||||
*/
|
||||
fun converter(name: String): Converter {
|
||||
val modelEntity: ModelEntity = Converter(name)
|
||||
this.addModelEntity(modelEntity)
|
||||
return modelEntity as Converter
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add model entity to the model.
|
||||
*/
|
||||
fun addModelEntity(modelEntity: ModelEntity) {
|
||||
if (!existModelEntity(modelEntity)) {
|
||||
this.entities[modelEntity.name] = modelEntity
|
||||
} else {
|
||||
throw IllegalArgumentException("Duplicate model entity.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether model already contains a model entity or not.
|
||||
*/
|
||||
private fun existModelEntity(modelEntity: ModelEntity): Boolean {
|
||||
return this.entities.containsKey(modelEntity.name)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new converter.
|
||||
*/
|
||||
fun createConverter(entity: ModelEntity, vararg inputs: ModelEntity): Converter {
|
||||
val converter = Converter(entity, *inputs)
|
||||
this.addConverter(converter)
|
||||
entity.converter = converter
|
||||
return converter
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add converter to model.
|
||||
*/
|
||||
private fun addConverter(converter: Converter) {
|
||||
this.converters.add(converter)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new module.
|
||||
*/
|
||||
fun createModule(moduleName: String, modelPath: String): Model {
|
||||
val moduleObject = Class.forName(modelPath).kotlin.createInstance()
|
||||
val module = moduleObject as Model
|
||||
module.name = moduleName
|
||||
this.addModule(module)
|
||||
return module
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add module to model.
|
||||
*/
|
||||
private fun addModule(module: Model) {
|
||||
if (!existModule(module)) {
|
||||
this.modules[module.name] = module
|
||||
} else {
|
||||
throw IllegalArgumentException("Duplicate model module.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether model already contains a module or not.
|
||||
*/
|
||||
private fun existModule(module: Model): Boolean {
|
||||
return this.modules.containsKey(module.name)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Print model entities.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "Model [entities = ${this.entities} ]"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Abstract class that represents SD entities: [Stock], [Flow] or [Constant].
|
||||
*
|
||||
* @property name Entity name.
|
||||
* @property currentValue Entity current value (during integrationType). Default 0.0.
|
||||
* @property previousValue Entity previous value (in previous step of integrationType). Default 0.0.
|
||||
* @property equation Entity equation (for integrationType). Default null.
|
||||
* @property isCurrentValueCalculated Flag: 'true' if the current value has already been calculated in integrationType
|
||||
* process, and (default) 'false' otherwise.
|
||||
* @property converter Entity converter (for converting one entity to another): default is 'null'.
|
||||
*/
|
||||
|
||||
abstract class ModelEntity(
|
||||
/**
|
||||
* Primary constructor.
|
||||
*/
|
||||
var name: String,
|
||||
var description: String = "",
|
||||
var unit: String = "",
|
||||
var currentValue: Double = 0.0,
|
||||
var previousValue: Double = 0.0,
|
||||
var isCurrentValueCalculated: Boolean = false,
|
||||
var converter: Converter? = null
|
||||
|
||||
) {
|
||||
|
||||
val _equation: (()->Double?)?
|
||||
get() {
|
||||
val invocationResult = equation?.invoke()
|
||||
return when(invocationResult) {
|
||||
is Double -> {
|
||||
this.isCurrentValueCalculated = true
|
||||
|
||||
{ invocationResult as Double? }
|
||||
}
|
||||
is ModelEntity -> {
|
||||
|
||||
if (invocationResult.isCurrentValueCalculated == false) {
|
||||
this.isCurrentValueCalculated = false
|
||||
} else {
|
||||
this.isCurrentValueCalculated = true
|
||||
}
|
||||
{ (invocationResult as ModelEntity).currentValue }
|
||||
}
|
||||
is Int -> {
|
||||
this.isCurrentValueCalculated = true
|
||||
{ invocationResult.toDouble() }
|
||||
}
|
||||
else -> {
|
||||
{ null }
|
||||
}
|
||||
}
|
||||
}
|
||||
var equation: (()->Any?)? = null
|
||||
|
||||
|
||||
/**
|
||||
* Overridden methods.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "ModelEntity[ name=${this.name}, value=${this.currentValue}, previousValue=${this.previousValue} ]"
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operator overloading: times.
|
||||
*/
|
||||
operator fun times(other: Any): Double {
|
||||
return when (other) {
|
||||
is ModelEntity -> this.currentValue * other.currentValue
|
||||
is Double -> this.currentValue * other
|
||||
is Int -> this.currentValue * (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operator overloading: plus.
|
||||
*/
|
||||
operator fun plus(other: Any): Double {
|
||||
return when(other) {
|
||||
is ModelEntity -> this.currentValue + other.currentValue
|
||||
is Double -> this.currentValue + other
|
||||
is Int -> this.currentValue + (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operator overloading: minus.
|
||||
*/
|
||||
operator fun minus(other: Any): Double {
|
||||
return when(other) {
|
||||
is ModelEntity -> this.currentValue - other.currentValue
|
||||
is Double -> this.currentValue - other
|
||||
is Int -> this.currentValue - (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operator overloading: div.
|
||||
*/
|
||||
operator fun div(other: Any): Double {
|
||||
return when(other) {
|
||||
is ModelEntity -> if (other != 0) this.currentValue / other.currentValue else 0.0
|
||||
is Double -> if (other != 0) this.currentValue / other else 0.0
|
||||
is Int -> this.currentValue / (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Operator overloading: unaryMinus.
|
||||
*/
|
||||
operator fun unaryMinus(): Double {
|
||||
return this.currentValue * (-1.0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For commutativity - extension function on Double: div.
|
||||
*/
|
||||
operator fun Double.div(other: Any): Double {
|
||||
return when (other) {
|
||||
is ModelEntity -> if (other != 0) this / other.currentValue else 0.0
|
||||
is Double -> if (other != 0) this / other else 0.0
|
||||
is Int -> this / (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For commutativity - extension function on Double: times.
|
||||
*/
|
||||
operator fun Double.times(other: Any): Double {
|
||||
return when (other) {
|
||||
is ModelEntity -> this * other.currentValue
|
||||
is Double -> this * other
|
||||
is Int -> this * (other.toDouble())
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Three entities of system dynamics models: [Stock], [Flow] and [Constant].
|
||||
* [Flow] and [Converter] are basically the same entity, but with different roles.
|
||||
*
|
||||
*/
|
||||
enum class ModelEntityType {
|
||||
STOCK, FLOW, CONSTANT, CONVERTER
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package hr.unipu.ksdtoolkit.entities
|
||||
|
||||
/**
|
||||
* Stock entity.
|
||||
*
|
||||
* Stock has one or more "input" and "outputs" [Flow] instances, integrates (accumulates) their difference or "change
|
||||
* rate", and represents a "memory" of a current state of the dynamics system.
|
||||
* Stocks can be only influenced by flows.
|
||||
*
|
||||
* @property name Stock name (only passed to the abstract class ModelEntity).
|
||||
*/
|
||||
class Stock(name: String) : ModelEntity(name) {
|
||||
|
||||
val _initialValue: (()->Double?)?
|
||||
get() {
|
||||
val invocationResult = initialValue?.invoke()
|
||||
return when(invocationResult) {
|
||||
is Double -> {
|
||||
{ invocationResult as Double? }
|
||||
}
|
||||
is ModelEntity -> {
|
||||
{ invocationResult.equation?.invoke().toString().toDouble() }
|
||||
}
|
||||
is Int -> {
|
||||
{ invocationResult.toDouble() }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
var initialValue: (()->Any?)? = null
|
||||
|
||||
|
||||
init {
|
||||
this.isCurrentValueCalculated = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package hr.unipu.ksdtoolkit.integration
|
||||
|
||||
/**
|
||||
* Euler method for numeric integrationType implementation.
|
||||
*
|
||||
* The class is extends [Integration] abstract class.
|
||||
*/
|
||||
class EulerIntegration : Integration() {
|
||||
|
||||
override fun integrate() {
|
||||
for(stock in stocks) {
|
||||
val calculatedValue = stock.currentValue + ( stock._equation?.invoke() ?: 0.0 ) * dt
|
||||
stock.currentValue = calculatedValue
|
||||
stock.isCurrentValueCalculated = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package hr.unipu.ksdtoolkit.integration
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Stock
|
||||
|
||||
/**
|
||||
* Abstract class that describes an integrationType process.
|
||||
*/
|
||||
abstract class Integration {
|
||||
|
||||
var dt: Double = 0.0
|
||||
var stocks: List<Stock> = arrayListOf()
|
||||
var converters: List<hr.unipu.ksdtoolkit.entities.Converter> = arrayListOf()
|
||||
|
||||
/**
|
||||
* Abstract method for numeric integrationType that has to be implemented (by methods: Euler, Runge-Kutta, etc.)
|
||||
*/
|
||||
abstract fun integrate()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package hr.unipu.ksdtoolkit.integration
|
||||
|
||||
/**
|
||||
* Runge-Kutta method for numeric integrationType implementation.
|
||||
*
|
||||
* The class is extends [Integration] abstract class.
|
||||
*/
|
||||
class RungeKuttaIntegration : Integration() {
|
||||
|
||||
override fun integrate() {
|
||||
val k = Array(this.stocks.size) { DoubleArray(4) }
|
||||
for (i in 0..3) {
|
||||
var j = 0
|
||||
for (stock in this.stocks) {
|
||||
k[j][i] = (stock._equation?.invoke() ?: 0.0) * this.dt
|
||||
if (i < 2)
|
||||
stock.currentValue = stock.previousValue + k[j][i] / 2
|
||||
else if (i == 2)
|
||||
stock.currentValue = stock.previousValue + k[j][i]
|
||||
else {
|
||||
val calculatedValue = stock.previousValue + k[j][0] / 6 + k[j][1] / 3 + k[j][2] / 3 + k[j][3] / 6
|
||||
stock.currentValue = calculatedValue
|
||||
}
|
||||
stock.isCurrentValueCalculated = true
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
|
||||
/**
|
||||
* SD model of Bass Diffusion.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
open class ModelBassDiffusion : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val INITIAL_TARGET_MARKET_SIZE_KEY = "INITIAL_TARGET_MARKET_SIZE"
|
||||
const val INITIAL_CUSTOMER_BASE_SIZE_KEY = "INITIAL_CUSTOMER_BASE_SIZE"
|
||||
const val ADVERTISING_BUDGET_KEY = "ADVERTISING_BUDGET"
|
||||
const val PERSONS_REACHED_PER_EURO_KEY = "PERSONS_REACHED_PER_EURO"
|
||||
const val ADVERTISING_SUCCESS_RATE_KEY = "ADVERTISING_SUCCESS_RATE"
|
||||
const val WORD_OF_MOUTH_CONTACT_RATE_KEY = "WORD_OF_MOUTH_CONTACT_RATE"
|
||||
const val WORD_OF_MOUTH_SUCCESS_RATE_KEY = "WORD_OF_MOUTH_SUCCESS_RATE"
|
||||
|
||||
const val INITIAL_TARGET_MARKET_SIZE_VALUE= 6e6 // [customer]
|
||||
const val INITIAL_CUSTOMER_BASE_SIZE_VALUE = 0.0 // [customer]
|
||||
const val ADVERTISING_BUDGET_VALUE = 10000.0 // [EUR/month]
|
||||
const val PERSONS_REACHED_PER_EURO_VALUE = 100.0 // [customer/EUR]
|
||||
const val ADVERTISING_SUCCESS_RATE_VALUE = 1.0 // [%]
|
||||
const val WORD_OF_MOUTH_CONTACT_RATE_VALUE = 1 // [1/month]
|
||||
const val WORD_OF_MOUTH_SUCCESS_RATE_VALUE = 10.0 // [%]
|
||||
|
||||
const val INITIAL_TIME_VALUE = 0.0
|
||||
const val FINAL_TIME_VALUE = 60.0
|
||||
const val TIME_STEP_VALUE = 0.25
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this // inheritance: Model()
|
||||
// alternative: Model(INITIAL_TIME_VALUE, FINAL_TIME_VALUE, TIME_STEP_VALUE, EulerIntegration())
|
||||
|
||||
// override default model properties:
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.integrationType = EulerIntegration()
|
||||
model.name = "Bass Diffusion Model" // name is optional
|
||||
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables
|
||||
val INITIAL_TARGET_MARKET_SIZE= model.constant(INITIAL_TARGET_MARKET_SIZE_KEY)
|
||||
val INITIAL_CUSTOMER_BASE_SIZE = model.constant(INITIAL_CUSTOMER_BASE_SIZE_KEY)
|
||||
val ADVERTISING_BUDGET = model.constant(ADVERTISING_BUDGET_KEY)
|
||||
val PERSONS_REACHED_PER_EURO = model.constant(PERSONS_REACHED_PER_EURO_KEY)
|
||||
val ADVERTISING_SUCCESS_RATE = model.constant(ADVERTISING_SUCCESS_RATE_KEY)
|
||||
val WORD_OF_MOUTH_CONTACT_RATE = model.constant(WORD_OF_MOUTH_CONTACT_RATE_KEY)
|
||||
val WORD_OF_MOUTH_SUCCESS_RATE = model.constant(WORD_OF_MOUTH_SUCCESS_RATE_KEY)
|
||||
|
||||
val marketSaturationPct = model.converter("marketSaturationPct")
|
||||
val potentialCustomersReachedThroughAdvertising = model.converter("potentialCustomersReachedThroughAdvertising")
|
||||
val acquisitionThroughAdvertising = model.converter("acquisitionThroughAdvertising")
|
||||
val potentialCustomersReachedThroughWordOfMouth = model.converter("potentialCustomersReachedThroughWordOfMouth")
|
||||
val acquisitionThroughWordOfMouth = model.converter("acquisitionThroughWordOfMouth")
|
||||
|
||||
// - 2b. Stocks
|
||||
val Customer_Base = model.stock("Customer_Base")
|
||||
val Advertising_Customers = model.stock("Advertising_Customers")
|
||||
val WordOfMouth_Customers = model.stock("WordOfMouth_Customers")
|
||||
|
||||
// - 2c. Flows
|
||||
val customerAcquisition = model.flow("customerAcquisition")
|
||||
val advCustomerIn = model.flow("advCustomerIn")
|
||||
val womCustomerIn = model.flow("womCustomerIn")
|
||||
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
|
||||
Customer_Base.initialValue = { INITIAL_CUSTOMER_BASE_SIZE }
|
||||
Advertising_Customers.initialValue = { 0.0 }
|
||||
WordOfMouth_Customers.initialValue = { 0.0 }
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Variables
|
||||
INITIAL_TARGET_MARKET_SIZE.equation = { INITIAL_TARGET_MARKET_SIZE_VALUE }
|
||||
INITIAL_CUSTOMER_BASE_SIZE.equation = { INITIAL_CUSTOMER_BASE_SIZE_VALUE }
|
||||
ADVERTISING_BUDGET.equation = { ADVERTISING_BUDGET_VALUE }
|
||||
PERSONS_REACHED_PER_EURO.equation = { PERSONS_REACHED_PER_EURO_VALUE }
|
||||
ADVERTISING_SUCCESS_RATE.equation = { ADVERTISING_SUCCESS_RATE_VALUE }
|
||||
WORD_OF_MOUTH_CONTACT_RATE.equation = { WORD_OF_MOUTH_CONTACT_RATE_VALUE }
|
||||
WORD_OF_MOUTH_SUCCESS_RATE.equation = { WORD_OF_MOUTH_SUCCESS_RATE_VALUE }
|
||||
|
||||
// - 4b. Stocks
|
||||
|
||||
Customer_Base.equation = { customerAcquisition }
|
||||
Advertising_Customers.equation = { advCustomerIn }
|
||||
WordOfMouth_Customers.equation = { womCustomerIn }
|
||||
|
||||
// - 4c. Flows
|
||||
customerAcquisition.equation = { advCustomerIn + womCustomerIn }
|
||||
advCustomerIn.equation = { acquisitionThroughAdvertising }
|
||||
womCustomerIn.equation = { acquisitionThroughWordOfMouth }
|
||||
|
||||
// - 4d. Converters:
|
||||
|
||||
marketSaturationPct.equation = { Customer_Base / INITIAL_TARGET_MARKET_SIZE * 100.0 }
|
||||
|
||||
potentialCustomersReachedThroughAdvertising.equation =
|
||||
{ PERSONS_REACHED_PER_EURO * ADVERTISING_BUDGET * (1.0 - marketSaturationPct / 100.0) }
|
||||
|
||||
acquisitionThroughAdvertising.equation =
|
||||
{ potentialCustomersReachedThroughAdvertising * ADVERTISING_SUCCESS_RATE / 100.0 }
|
||||
|
||||
potentialCustomersReachedThroughWordOfMouth.equation =
|
||||
{ WORD_OF_MOUTH_CONTACT_RATE * Customer_Base * (1 - marketSaturationPct / 100.0) }
|
||||
|
||||
acquisitionThroughWordOfMouth.equation =
|
||||
{ potentialCustomersReachedThroughWordOfMouth * WORD_OF_MOUTH_SUCCESS_RATE / 100.0 }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.modules.ModuleGenericCompoundDecrease
|
||||
|
||||
/**
|
||||
* SD model of Simple Compound Interest.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
open class ModelGenericSD : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val CONSTANT_KEY = "CONSTANT"
|
||||
const val CONVERTER_KEY = "converter"
|
||||
const val INITIAL_STOCK_KEY = "INITIAL_STOCK"
|
||||
const val INFLOW_KEY = "inflow"
|
||||
const val OUTFLOW_KEY = "outflow"
|
||||
const val STOCK_KEY = "Stock"
|
||||
|
||||
const val CONSTANT_VALUE = 10 // [%]
|
||||
const val INITIAL_STOCK_VALUE = 100 // [€]
|
||||
const val INITIAL_TIME_VALUE = 0 // [month]
|
||||
const val FINAL_TIME_VALUE = 120 // [month]
|
||||
const val TIME_STEP_VALUE = 0.25 // [month]
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this // inheritance: Model()
|
||||
|
||||
/*
|
||||
val model = Model(
|
||||
initialTime = 0,
|
||||
finalTime = 100,
|
||||
timeStep = 0.25,
|
||||
integrationType = EulerIntegration()
|
||||
)
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
val model = Model(0, 100, 0.25, EulerIntegration())
|
||||
// alternative: Model(INITIAL_TIME_VALUE, FINAL_TIME_VALUE, TIME_STEP_VALUE, EulerIntegration())
|
||||
*/
|
||||
|
||||
|
||||
// override default model properties:
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.integrationType = EulerIntegration()
|
||||
model.name = "Generic SD Model" // name is optional
|
||||
model.timeUnit = "month" // unit is optional
|
||||
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables (Constants)
|
||||
val CONSTANT = model.constant(CONSTANT_KEY)
|
||||
val INITIAL_STOCK = model.constant(INITIAL_STOCK_KEY)
|
||||
|
||||
// - 2b. Variables (Converters)
|
||||
val converter = model.converter(CONVERTER_KEY)
|
||||
|
||||
// - 2c. Stocks
|
||||
val Stock = model.stock(STOCK_KEY)
|
||||
|
||||
// - 2d. Flows
|
||||
val inflow = model.flow(INFLOW_KEY)
|
||||
val outflow = model.flow(OUTFLOW_KEY)
|
||||
|
||||
// - 2e. Modules
|
||||
val Module = model.createModule(
|
||||
"Module",
|
||||
"hr.unipu.ksdtoolkit.modules.ModuleGenericCompoundDecrease"
|
||||
) as ModuleGenericCompoundDecrease
|
||||
|
||||
|
||||
// - 2f. (Optional): Entities' descriptions
|
||||
CONSTANT.description = "Annual flow rate in [%/year]"
|
||||
INITIAL_STOCK.description = "Initial capital in [EUR] in the beginning of the simulation."
|
||||
converter.description = "Converts percentage to decimal."
|
||||
Stock.description = "Accumulated capital in [EUR] at specific point in time."
|
||||
inflow.description = "Interest inflow in [EUR / chosen unit of time], e.g. [EUR/month]."
|
||||
outflow.description = "Interest outflow in [EUR / chosen unit of time], e.g. [EUR/month]."
|
||||
|
||||
// - 2g. (Optional): Entities' units
|
||||
CONSTANT.unit = "%/year"
|
||||
INITIAL_STOCK.unit = "€"
|
||||
Stock.unit = "€"
|
||||
inflow.unit = "€/month"
|
||||
outflow.unit = "€/month"
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
Stock.initialValue = { INITIAL_STOCK } // Accepts: Double, Int or ModelEntity
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Constants
|
||||
CONSTANT.equation = { CONSTANT_VALUE }
|
||||
INITIAL_STOCK.equation = { INITIAL_STOCK_VALUE }
|
||||
|
||||
// - 4b. Converters
|
||||
converter.equation = { CONSTANT / 100.0 / 12.0 }
|
||||
|
||||
// - 4c. Stocks
|
||||
Stock.equation = { inflow - outflow } // Function type can be either Double or ModelEntity.
|
||||
|
||||
// - 4d. Flows:
|
||||
inflow.equation = { Stock * converter } // Simplified converters so that only equations are used.
|
||||
outflow.equation = { Module.outflow } // Alternative, instead of lambda, member reference: Module::outflow
|
||||
|
||||
// - 4e. Modules:
|
||||
Module.inflow.equation = { outflow }
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.integration.RungeKuttaIntegration
|
||||
|
||||
/**
|
||||
* Inheritance of Simple Compound Interest Model.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
class ModelInheritedCompoundInterest : ModelSimpleCompoundInterest() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val INTEREST_RATE_KEY = "INTEREST_RATE"
|
||||
const val INITIAL_CAPITAL_KEY = "INITIAL_CAPITAL"
|
||||
const val INTEREST_KEY = "interest"
|
||||
const val CAPITAL_KEY = "Capital"
|
||||
|
||||
const val INTEREST_RATE_VALUE = -0.1 / 12
|
||||
const val INITIAL_CAPITAL_VALUE = 200.0
|
||||
const val INITIAL_TIME_VALUE = 0.0
|
||||
const val FINAL_TIME_VALUE = 240.0
|
||||
const val TIME_STEP_VALUE = 0.25
|
||||
}
|
||||
|
||||
|
||||
// 1. Create the model (with the parameters)
|
||||
val modelInherited = this // inheritance: ModelSimpleCompoundInterest()
|
||||
//val modelInherited = object : ModelSimpleCompoundInterest() {}
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// overriding default model properties:
|
||||
modelInherited.initialTime = INITIAL_TIME_VALUE
|
||||
modelInherited.finalTime = FINAL_TIME_VALUE
|
||||
modelInherited.timeStep = TIME_STEP_VALUE
|
||||
modelInherited.integrationType = RungeKuttaIntegration()
|
||||
modelInherited.name = "Inherited Compound Interest Model"
|
||||
|
||||
// changing inherited model constants
|
||||
modelInherited.entities[INITIAL_CAPITAL_KEY]?.equation = { INITIAL_CAPITAL_VALUE }
|
||||
modelInherited.entities[INTEREST_RATE_KEY]?.equation = { INTEREST_RATE_VALUE }
|
||||
|
||||
modelInherited.INITIAL_CAPITAL.equation = { 200.0 }
|
||||
modelInherited.INTEREST_RATE.equation = { -0.1 / 12 }
|
||||
}
|
||||
|
||||
val ModuleSimpleCompound1 = modelInherited.createModule(
|
||||
"ModuleSimpleCompound1",
|
||||
"hr.unipu.ksdtoolkit.models.ModelSimpleCompoundInterest"
|
||||
) as ModelSimpleCompoundInterest
|
||||
|
||||
val ModuleSimpleCompound2 = object : ModelSimpleCompoundInterest() {}
|
||||
|
||||
init {
|
||||
modelInherited.modules["ModuleSimpleCompound1"]?.entities!!["INITIAl_CAPITAL"]?.equation = { 1000.0 }
|
||||
|
||||
modelInherited.ModuleSimpleCompound1.INITIAL_CAPITAL.equation = { 1000.0 }
|
||||
|
||||
modelInherited.ModuleSimpleCompound2.INITIAL_CAPITAL.equation = { 1000.0 }
|
||||
}
|
||||
|
||||
val some_converter_in_parent = modelInherited.converter("some_converter_in_parent")
|
||||
|
||||
init {
|
||||
some_converter_in_parent.equation = { modelInherited.ModuleSimpleCompound1.INITIAL_CAPITAL }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.entities.div
|
||||
import hr.unipu.ksdtoolkit.entities.times
|
||||
import hr.unipu.ksdtoolkit.integration.RungeKuttaIntegration
|
||||
|
||||
/**
|
||||
* SD model of Bass Diffusion.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
open class ModelInnovationDiffusion : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val TOTAL_POPULATION_KEY = "TOTAL_POPULATION"
|
||||
const val ADVERTISING_EFFECTIVENESS_KEY = "ADVERTISING_EFFECTIVENESS"
|
||||
const val CONTACT_RATE_KEY = "CONTACT_RATE"
|
||||
const val ADOPTION_FRACTION_KEY = "ADOPTION_FRACTION"
|
||||
|
||||
const val TOTAL_POPULATION_VALUE = 10000.0 // [customer]
|
||||
const val ADVERTISING_EFFECTIVENESS_VALUE = 0.011 // [1/year]
|
||||
const val CONTACT_RATE_VALUE = 100.0 // [1/year]
|
||||
const val ADOPTION_FRACTION_VALUE = 0.015 // [ ]
|
||||
|
||||
const val INITIAL_TIME_VALUE = 0.0
|
||||
const val FINAL_TIME_VALUE = 10.0
|
||||
const val TIME_STEP_VALUE = 0.25
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this // inheritance: Model()
|
||||
// alternative: Model(INITIAL_TIME_VALUE, FINAL_TIME_VALUE, TIME_STEP_VALUE, EulerIntegration())
|
||||
|
||||
// override default model properties:
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.timeUnit = "year" // optional
|
||||
|
||||
//model.integrationType = EulerIntegration()
|
||||
model.integrationType = RungeKuttaIntegration()
|
||||
model.name = "Innovation/Product Diffusion Model"
|
||||
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables (Constants)
|
||||
val TOTAL_POPULATION = model.constant("TOTAL_POPULATION")
|
||||
val ADVERTISING_EFFECTIVENESS = model.constant("ADVERTISING_EFFECTIVENESS")
|
||||
val CONTACT_RATE = model.constant("CONTACT_RATE")
|
||||
val ADOPTION_FRACTION = model.constant("ADOPTION_FRACTION")
|
||||
|
||||
// - 2b. Variables (Converters)
|
||||
val adoptionFromAdvertising = model.converter("adoptionFromAdvertising")
|
||||
val adoptionFromWordOfMouth = model.converter("adoptionFromWordOfMouth")
|
||||
|
||||
// - 2c. Stocks
|
||||
val Potential_Adopters = model.stock("Potential_Adopters")
|
||||
val Adopters = model.stock("Adopters")
|
||||
|
||||
// - 2d. Flows
|
||||
val adoptionRate = model.flow("adoptionRate")
|
||||
|
||||
// - 2g. (Optional): Entities' units
|
||||
TOTAL_POPULATION.unit = "customer"
|
||||
ADVERTISING_EFFECTIVENESS.unit = "1/year"
|
||||
CONTACT_RATE.unit = "1/year"
|
||||
ADOPTION_FRACTION.unit = ""
|
||||
Potential_Adopters.unit = "customer"
|
||||
Adopters.unit = "customer"
|
||||
adoptionRate.unit = "customer/year"
|
||||
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
Potential_Adopters.initialValue = { TOTAL_POPULATION }
|
||||
Adopters.initialValue = { 0.0 }
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Constants:
|
||||
TOTAL_POPULATION.equation = { TOTAL_POPULATION_VALUE }
|
||||
ADVERTISING_EFFECTIVENESS.equation = { ADVERTISING_EFFECTIVENESS_VALUE }
|
||||
CONTACT_RATE.equation = { CONTACT_RATE_VALUE }
|
||||
ADOPTION_FRACTION.equation = { ADOPTION_FRACTION_VALUE }
|
||||
|
||||
// - 4b. Converters:
|
||||
adoptionFromAdvertising.equation = { Potential_Adopters * ADVERTISING_EFFECTIVENESS }
|
||||
adoptionFromWordOfMouth.equation = { CONTACT_RATE * ADOPTION_FRACTION *
|
||||
Potential_Adopters * Adopters / TOTAL_POPULATION }
|
||||
|
||||
// - 4c. Stocks
|
||||
Potential_Adopters.equation = { - adoptionRate }
|
||||
Adopters.equation = { adoptionRate }
|
||||
|
||||
// - 4d. Flows
|
||||
adoptionRate.equation = { adoptionFromAdvertising + adoptionFromWordOfMouth }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
|
||||
/**
|
||||
* SD model of Simple Compound Interest.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
open class ModelSimpleCompoundInterest : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val INTEREST_RATE_KEY = "INTEREST_RATE"
|
||||
const val INITIAL_CAPITAL_KEY = "INITIAL_CAPITAL"
|
||||
const val INTEREST_KEY = "interest"
|
||||
const val CAPITAL_KEY = "Capital"
|
||||
|
||||
const val INTEREST_RATE_VALUE = 0.1 / 12
|
||||
const val INITIAL_CAPITAL_VALUE = 100.0
|
||||
const val INITIAL_TIME_VALUE = 0.0
|
||||
const val FINAL_TIME_VALUE = 120.0
|
||||
const val TIME_STEP_VALUE = 0.25
|
||||
}
|
||||
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this // inheritance: Model()
|
||||
// alternative:
|
||||
//val model = Model(INITIAL_TIME_VALUE, FINAL_TIME_VALUE, TIME_STEP_VALUE, EulerIntegration())
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// override default model properties:
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.integrationType = EulerIntegration()
|
||||
model.name = "Simple Compound Interest Model" // name is optional
|
||||
model.timeUnit = "month" // timeUnit is optional
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables
|
||||
val INTEREST_RATE = model.constant(INTEREST_RATE_KEY) // Using: constant() function
|
||||
val INITIAL_CAPITAL = model.constant(INITIAL_CAPITAL_KEY) // Using: constant() function
|
||||
|
||||
// - 2b. Stocks
|
||||
val Capital = model.stock(CAPITAL_KEY) // Using: stock() function
|
||||
|
||||
// - 2c. Flows
|
||||
val interest = model.flow(INTEREST_KEY) // Using: flow() function
|
||||
|
||||
// - 2e. Modules
|
||||
|
||||
|
||||
init {
|
||||
|
||||
// - 2f. (Optional): Entities' descriptions
|
||||
INTEREST_RATE.description = "Annual flow rate in [%/year]"
|
||||
INITIAL_CAPITAL.description = "Initial capital in [EUR] in the beginning of the simulation."
|
||||
Capital.description = "Accumulated capital in [EUR] at specific point in time."
|
||||
interest.description = "Interest flow in [EUR / chosen unit of time], " +
|
||||
"e.g. [EUR/month] due to paying flow on the Capital."
|
||||
|
||||
// - 2g. (Optional): Entities' units
|
||||
INTEREST_RATE.unit = "%/year"
|
||||
INITIAL_CAPITAL.unit = "€"
|
||||
Capital.unit = "€"
|
||||
interest.unit = "€/month"
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
Capital.initialValue = { INITIAL_CAPITAL } // Accepts: Double or ModelEntity
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Variables
|
||||
INTEREST_RATE.equation = { INTEREST_RATE_VALUE }
|
||||
INITIAL_CAPITAL.equation = { INITIAL_CAPITAL_VALUE }
|
||||
|
||||
// - 4b. Stocks
|
||||
Capital.equation = { interest } // Function type can be either Double or ModelEntity.
|
||||
|
||||
// - 4c. Converters:
|
||||
interest.equation = { Capital * INTEREST_RATE }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package hr.unipu.ksdtoolkit.models
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
|
||||
/**
|
||||
* SD model of Simple Compound Interest.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
open class ModelTestSpeed : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val INITIAL_STOCK_KEY = "INITIAL_STOCK"
|
||||
const val FLOW_KEY = "flow"
|
||||
const val STOCK_KEY = "Stock"
|
||||
|
||||
const val INITIAL_STOCK_VALUE = 1 // []
|
||||
const val FLOW_VALUE = 1 // []
|
||||
const val INITIAL_TIME_VALUE = 0 // []
|
||||
const val FINAL_TIME_VALUE = 1e7 // [] 1e7 for comparison (Vensim = cca30sec., BPTK-Py = cca153sec.)
|
||||
const val TIME_STEP_VALUE = 1 // []
|
||||
}
|
||||
|
||||
|
||||
// Time_steps=1e3 -> Time elapsed=0.093 -> 0.036 sec
|
||||
// Time_steps=1e4 -> Time elapsed=0.192 -> 0.099 sec
|
||||
// Time_steps=1e5 -> Time elapsed=0.704 -> 0.389 sec
|
||||
// Time_steps=1e6 -> Time elapsed=4.367 -> 1.034 sec
|
||||
// Time_steps=1e7 -> Time elapsed=27.99 -> 7.082 sec
|
||||
|
||||
init {
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this
|
||||
|
||||
// override default model properties:
|
||||
model.initialTime = INITIAL_TIME_VALUE
|
||||
model.finalTime = FINAL_TIME_VALUE
|
||||
model.timeStep = TIME_STEP_VALUE
|
||||
model.integrationType = EulerIntegration()
|
||||
model.name = "SD Model for testing speed" // name is optional
|
||||
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables (Constants)
|
||||
val INITIAL_STOCK = model.constant(INITIAL_STOCK_KEY)
|
||||
|
||||
// - 2b. Variables (Converters)
|
||||
|
||||
// - 2c. Stocks
|
||||
val Stock = model.stock(STOCK_KEY)
|
||||
|
||||
// - 2d. Flows
|
||||
val flow = model.flow(FLOW_KEY)
|
||||
|
||||
// - 2e. Modules
|
||||
|
||||
// - 2f. (Optional): Entities' descriptions
|
||||
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
Stock.initialValue = { INITIAL_STOCK } // Accepts: Double, Int or ModelEntity
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Constants
|
||||
INITIAL_STOCK.equation = { INITIAL_STOCK_VALUE }
|
||||
|
||||
// - 4b. Converters
|
||||
|
||||
// - 4c. Stocks
|
||||
Stock.equation = { flow } // Function type can be either Double or ModelEntity.
|
||||
|
||||
// - 4d. Flows:
|
||||
flow.equation = { 1.0 } // Simplified converters so that only equations are used.
|
||||
|
||||
// - 4e. Modules:
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package hr.unipu.ksdtoolkit.modules
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
|
||||
/**
|
||||
* SD model of Simple Compound Interest.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
open class ModuleGenericCompoundDecrease : Model() {
|
||||
|
||||
// Static properties:
|
||||
companion object {
|
||||
const val CONSTANT_KEY = "CONSTANT"
|
||||
const val CONVERTER_KEY = "converter"
|
||||
const val INITIAL_STOCK_KEY = "INITIAL_STOCK"
|
||||
const val OUTFLOW_KEY = "outflow"
|
||||
const val INFLOW_KEY = "inflow"
|
||||
const val STOCK_KEY = "Stock"
|
||||
|
||||
const val CONSTANT_VALUE = 10.0 // [%]
|
||||
const val INITIAL_STOCK_VALUE = 100.0 // [€]
|
||||
}
|
||||
|
||||
|
||||
// 1. Create the model (with setup of: time boundaries & time step & integrationType type)
|
||||
val model = this // inheritance: Model()
|
||||
// alternative: Model(INITIAL_TIME_VALUE, FINAL_TIME_VALUE, TIME_STEP_VALUE, EulerIntegration())
|
||||
|
||||
|
||||
init {
|
||||
// override default model properties:
|
||||
model.name = "Generic Compound Decrease Module" // name is optional
|
||||
}
|
||||
|
||||
|
||||
// 2. Create all system elements:
|
||||
|
||||
// - 2a. Variables: Constants
|
||||
val CONSTANT = model.constant(CONSTANT_KEY)
|
||||
val INITIAL_STOCK = model.constant(INITIAL_STOCK_KEY)
|
||||
|
||||
// - 2b. Variable: Converters
|
||||
val converter = model.converter(CONVERTER_KEY)
|
||||
|
||||
// - 2c. Stocks
|
||||
val Stock = model.stock(STOCK_KEY)
|
||||
|
||||
// - 2d. Flows
|
||||
val inflow = model.flow(INFLOW_KEY)
|
||||
val outflow = model.flow(OUTFLOW_KEY)
|
||||
|
||||
// - 2e. Modules
|
||||
// n/a
|
||||
|
||||
init {
|
||||
// - 2f. (Optional): Entities' descriptions
|
||||
CONSTANT.description = "Annual flow rate in [%/year]"
|
||||
INITIAL_STOCK.description = "Initial capital in [EUR] in the beginning of the simulation."
|
||||
converter.description = "Converts percentage to decimal."
|
||||
Stock.description = "Accumulated capital in [EUR] at specific point in time."
|
||||
inflow.description = "Interest inflow in [EUR / chosen unit of time], e.g. [EUR/month]"
|
||||
outflow.description = "Interest outflow in [EUR / chosen unit of time], e.g. [EUR/month]"
|
||||
|
||||
|
||||
|
||||
// 3. Initial values:
|
||||
|
||||
// - 3a. Stocks
|
||||
Stock.initialValue = { INITIAL_STOCK } // Accepts: Double, Int or ModelEntity
|
||||
|
||||
|
||||
|
||||
// 4. Equations:
|
||||
|
||||
// - 4a. Variables (Constants)
|
||||
CONSTANT.equation = { CONSTANT_VALUE }
|
||||
INITIAL_STOCK.equation = { INITIAL_STOCK_VALUE }
|
||||
|
||||
// - 4b. Variables (Converters)
|
||||
converter.equation = { CONSTANT / 100.0 / 12.0 }
|
||||
|
||||
// - 4c. Stocks
|
||||
Stock.equation = { inflow - outflow } // Function type can be either Double or ModelEntity.
|
||||
|
||||
// - 4d. Flows:
|
||||
inflow.equation = { } // Not defined. Can be also defined as: { null }. Module input.
|
||||
outflow.equation = { Stock * converter } // Simplified converters so that only equations are used.
|
||||
|
||||
// - 4e. Modules:
|
||||
// n/a
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package hr.unipu.ksdtoolkit.outputs
|
||||
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* Class for exporting simulation data in CSV format
|
||||
* (also implements the [SimulationEventListener] interface).
|
||||
*/
|
||||
class CsvExporter(private val csvFile: String = "output.csv", // Default name.
|
||||
private val separator: String = ";" // Default separator.
|
||||
) : ISimulationEventHandler {
|
||||
|
||||
val sb = StringBuilder()
|
||||
|
||||
private val string: String
|
||||
get() {
|
||||
val last = sb.lastIndexOf("\n")
|
||||
if (last >= 0 && sb.length - last == 1) {
|
||||
sb.delete(last, sb.length)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of method simulationInitialized() from ISimulationEventHandler interface.
|
||||
* Method 1) clears StringBuilder content and 2) writes simulation data, after initialization.
|
||||
*/
|
||||
override fun simulationInitialized(model: Model) {
|
||||
clearContent()
|
||||
writeTimeStepValues(model.modelEntitiesKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of method simulationInitialized() from ISimulationEventHandler interface.
|
||||
* Method 1) clears StringBuilder content and 2) writes simulation data, after every time step.
|
||||
*/
|
||||
override fun timeStepCalculated(model: Model) {
|
||||
writeTimeStepValues(model.modelEntitiesValues)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of method simulationInitialized() from ISimulationEventHandler interface.
|
||||
* Method saves simulation data into CSV file, after simulation is finished.
|
||||
*/
|
||||
override fun simulationFinished(model: Model) {
|
||||
saveFile()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear CSV content.
|
||||
*/
|
||||
private fun clearContent() {
|
||||
sb.delete(0, sb.length)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write all simulation data for one timestep to the CSV.
|
||||
*
|
||||
* @param modelEntityValues values of the model entities.
|
||||
*/
|
||||
private fun writeTimeStepValues(modelEntityValues: List<String>) {
|
||||
var first = true
|
||||
for (value in modelEntityValues) {
|
||||
if (!first) {
|
||||
sb.append(separator)
|
||||
}
|
||||
sb.append(value)
|
||||
first = false
|
||||
}
|
||||
sb.append("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to CSV file.
|
||||
*/
|
||||
private fun saveFile() {
|
||||
try {
|
||||
FileWriter(csvFile).use { writer ->
|
||||
writer.append(string)
|
||||
writer.flush()
|
||||
LoggerFactory.getLogger(javaClass).info("CSV file saved.")
|
||||
}
|
||||
} catch (exception: IOException) {
|
||||
LoggerFactory.getLogger(javaClass).error("Unable to save file.", exception)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package hr.unipu.ksdtoolkit.outputs
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import javafx.application.Application.launch
|
||||
import javafx.application.Platform
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import javafx.application.Application
|
||||
import javafx.stage.Stage
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
|
||||
/**
|
||||
* Class that implements the [SimulationEventListener] interface and controls the chart plotting.
|
||||
*/
|
||||
class PngExporter(fileName: String) : ISimulationEventHandler {
|
||||
|
||||
companion object {
|
||||
lateinit var simulation: Simulation
|
||||
}
|
||||
|
||||
init {
|
||||
PngExporterApp.fileName = fileName
|
||||
PngExporterApp.model = simulation.model
|
||||
PngExporterApp.simulation = simulation
|
||||
}
|
||||
|
||||
override fun simulationInitialized(model: Model) {
|
||||
PngExporterApp.addSeries(model.modelEntitiesKeys)
|
||||
}
|
||||
|
||||
override fun timeStepCalculated(model: Model) {
|
||||
PngExporterApp.addValues(model.modelEntitiesValues, model.currentTime)
|
||||
}
|
||||
|
||||
override fun simulationFinished(model: Model) {
|
||||
// Application is not just a window, it's a Process.
|
||||
// Thus only one Application.launch() is allowed per VM.
|
||||
try {
|
||||
LoggerFactory.getLogger(javaClass).info("PngExporterApp launching attempt.")
|
||||
PngExporterApp.pngExporterListener = this
|
||||
launch(PngExporterApp::class.java)
|
||||
|
||||
} catch (exception: IllegalStateException) {
|
||||
LoggerFactory.getLogger(javaClass).error("PngExporterApp launching failed.", exception)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package hr.unipu.ksdtoolkit.outputs
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import javafx.application.Application
|
||||
import javafx.application.Platform
|
||||
import javafx.embed.swing.SwingFXUtils
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.chart.LineChart
|
||||
import javafx.scene.chart.NumberAxis
|
||||
import javafx.scene.chart.XYChart
|
||||
import javafx.scene.chart.XYChart.Series
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.stage.Stage
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import javax.imageio.ImageIO
|
||||
import java.io.File
|
||||
import java.text.NumberFormat
|
||||
import java.text.ParseException
|
||||
import java.util.ArrayList
|
||||
import java.util.Locale
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
/**
|
||||
* JavaFX app for chart plotting & exporting.
|
||||
*/
|
||||
class PngExporterApp : Application() {
|
||||
|
||||
companion object {
|
||||
|
||||
lateinit var model: Model
|
||||
lateinit var simulation: Simulation
|
||||
lateinit var pngExporterListener: ISimulationEventHandler
|
||||
|
||||
var fileName: String = "outputs.png"
|
||||
lateinit var series: ArrayList<Series<Number, Number>>
|
||||
private var width = 800.0
|
||||
private var height = 600.0
|
||||
|
||||
/**
|
||||
* Add series names to the chart.
|
||||
*
|
||||
* @param modelEntityNames list of model entity names.
|
||||
*/
|
||||
fun addSeries(modelEntityNames: List<String>) {
|
||||
series = ArrayList<Series<Number, Number>>()
|
||||
for (modelEntityName in modelEntityNames) {
|
||||
val s = Series<Number, Number>()
|
||||
s.name = modelEntityName
|
||||
series.add(s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values to the chart series.
|
||||
*
|
||||
* @param modelEntityValues list of model entity values.
|
||||
* @param currentTime current model time.
|
||||
*/
|
||||
fun addValues(modelEntityValues: List<String>, currentTime: Double) {
|
||||
for (i in modelEntityValues.indices) {
|
||||
val valueString = modelEntityValues[i]
|
||||
val format = NumberFormat.getInstance(Locale.US)
|
||||
val number: Number
|
||||
try {
|
||||
number = format.parse(valueString)
|
||||
val value = number.toDouble()
|
||||
series[i].data?.add(XYChart.Data(currentTime, value))
|
||||
} catch (exception: ParseException) {
|
||||
LoggerFactory.getLogger(javaClass).error("Error with parsing model entity values.", exception)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set scene width and height.
|
||||
*
|
||||
* @param width width.
|
||||
* @param height height.
|
||||
*/
|
||||
fun setSize(width: Double, height: Double) {
|
||||
Companion.width = width
|
||||
Companion.height = height
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the chart as an image.
|
||||
*
|
||||
* @param scene Scene
|
||||
*/
|
||||
private fun saveToFile(scene: Scene) {
|
||||
val image = scene.snapshot(null)
|
||||
val outputFile = File(fileName)
|
||||
val bImage = SwingFXUtils.fromFXImage(image, null)
|
||||
ImageIO.write(bImage, "png", outputFile)
|
||||
LoggerFactory.getLogger(javaClass).info("PNG file saved.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("PngExporterApp started.")
|
||||
|
||||
val root = Group()
|
||||
|
||||
val scene = Scene(root, width, height)
|
||||
|
||||
val lineChart = createLineChart(model.name)
|
||||
lineChart.createSymbols = false
|
||||
lineChart.stylesheets.add("/Style2.css")
|
||||
lineChart.data.addAll(series)
|
||||
|
||||
val borderPane = BorderPane()
|
||||
borderPane.prefHeightProperty().bind(scene.heightProperty())
|
||||
borderPane.prefWidthProperty().bind(scene.widthProperty())
|
||||
borderPane.center = lineChart
|
||||
|
||||
root.children.add(borderPane)
|
||||
|
||||
saveToFile(scene)
|
||||
|
||||
stage.scene = scene
|
||||
stage.show()
|
||||
|
||||
val doOutputHandlersContainOtherJavaFxApp = simulation.outputHandlers.any {
|
||||
it.javaClass.name == "hr.unipu.ksdtoolkit.outputs.WinSimulator"
|
||||
}
|
||||
if (doOutputHandlersContainOtherJavaFxApp) {
|
||||
simulation.outputHandlers.remove(pngExporterListener)
|
||||
Platform.setImplicitExit(true)
|
||||
Platform.runLater { WinSimulatorApp().start(Stage()) }
|
||||
}
|
||||
LoggerFactory.getLogger(javaClass).info("PngExporterApp exit.")
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create LineChart.
|
||||
*
|
||||
* @param title line chart title
|
||||
* @return line chart
|
||||
*/
|
||||
private fun createLineChart(title: String): LineChart<Number, Number> {
|
||||
|
||||
val xAxis = NumberAxis()
|
||||
val yAxis = NumberAxis()
|
||||
xAxis.label = "Time [${model.timeUnit}]"
|
||||
yAxis.label = "Value []"
|
||||
|
||||
val lineChart = LineChart(xAxis, yAxis)
|
||||
|
||||
lineChart.animated = false
|
||||
|
||||
setAxisBounds(lineChart,
|
||||
min = simulation.model.initialTime.toDouble(),
|
||||
max = simulation.model.finalTime.toDouble(),
|
||||
isXAxis = true
|
||||
)
|
||||
|
||||
lineChart.title = title
|
||||
return lineChart
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setting axis bounds.
|
||||
*/
|
||||
fun setAxisBounds(myChart: LineChart<Number, Number>, min: Double, max: Double, isXAxis: Boolean) {
|
||||
val axis: NumberAxis = if (isXAxis) {
|
||||
myChart.xAxis as NumberAxis
|
||||
} else {
|
||||
myChart.yAxis as NumberAxis
|
||||
}
|
||||
axis.isAutoRanging = false
|
||||
axis.lowerBound = min
|
||||
axis.upperBound = max
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package hr.unipu.ksdtoolkit.outputs
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Constant
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import javafx.application.Application.launch
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
/**
|
||||
* Class that implements the [ISimulationEventHandler] interface and controls the chart printing.
|
||||
*
|
||||
*/
|
||||
class WinSimulator() : ISimulationEventHandler {
|
||||
|
||||
companion object {
|
||||
lateinit var simulation: Simulation
|
||||
}
|
||||
|
||||
init {
|
||||
WinSimulatorApp.simulation = simulation
|
||||
}
|
||||
|
||||
override fun simulationInitialized(model: Model) {
|
||||
WinSimulatorApp.addSeriesNames(model.modelEntitiesKeys)
|
||||
WinSimulatorApp.addSeriesConstants(model.entities.filterValues { it is Constant })
|
||||
|
||||
}
|
||||
|
||||
override fun timeStepCalculated(model: Model) {
|
||||
WinSimulatorApp.addSeriesValues(model.modelEntitiesValues, model.currentTime)
|
||||
}
|
||||
|
||||
override fun simulationFinished(model: Model) {
|
||||
try {
|
||||
// Application is not just a window, it's a Process.
|
||||
// Thus only one Application#launch() is allowed per VM.
|
||||
LoggerFactory.getLogger(javaClass).info("WinSimulatorApp launching attempt.")
|
||||
WinSimulatorApp.winSimulatorListener = this
|
||||
launch(WinSimulatorApp::class.java)
|
||||
|
||||
} catch (exception: IllegalStateException) {
|
||||
LoggerFactory.getLogger(javaClass).error("WinSimulatorApp launching failed.", exception)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
package hr.unipu.ksdtoolkit.outputs
|
||||
|
||||
import javafx.application.Application
|
||||
import javafx.embed.swing.SwingFXUtils
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.event.EventHandler
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.Cursor
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.chart.LineChart
|
||||
import javafx.scene.chart.NumberAxis
|
||||
import javafx.scene.chart.XYChart
|
||||
import javafx.scene.chart.XYChart.Series
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.input.MouseButton
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.Stage
|
||||
import hr.unipu.ksdtoolkit.entities.ModelEntity
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import javafx.application.Platform
|
||||
import javafx.event.Event
|
||||
import javafx.scene.input.KeyCode
|
||||
import javafx.scene.input.KeyEvent
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import javax.imageio.ImageIO
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.text.NumberFormat
|
||||
import java.text.ParseException
|
||||
import java.util.ArrayList
|
||||
import java.util.Locale
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* JavaFX app for chart plotting and interactive simulation.
|
||||
*/
|
||||
class WinSimulatorApp : Application() {
|
||||
|
||||
companion object {
|
||||
|
||||
private lateinit var scene: Scene
|
||||
private lateinit var series: ArrayList<Series<Number, Number>>
|
||||
private lateinit var seriesConstants: ArrayList<Series<Number, Number>>
|
||||
private lateinit var checkBoxes: ArrayList<CheckBox>
|
||||
private lateinit var textFields: ArrayList<VBox>
|
||||
private lateinit var lineChart: LineChart<Number, Number>
|
||||
private var width = 800.0
|
||||
private var height = 600.0
|
||||
|
||||
|
||||
lateinit var simulation: Simulation
|
||||
lateinit var winSimulatorListener: ISimulationEventHandler
|
||||
|
||||
/**
|
||||
* Add names of entities to names of series.
|
||||
*
|
||||
* @param modelEntityNames list of model entity names.
|
||||
*/
|
||||
fun addSeriesNames(modelEntityNames: List<String>) {
|
||||
series = ArrayList()
|
||||
for (modelEntityName in modelEntityNames) {
|
||||
val s = Series<Number, Number>()
|
||||
s.name = modelEntityName
|
||||
series.add(s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add constants for text fields.
|
||||
*
|
||||
* @param modelEntityConstants list of model entity (constants) names.
|
||||
*/
|
||||
fun addSeriesConstants(modelEntityConstants: Map<String, ModelEntity>) {
|
||||
seriesConstants = ArrayList()
|
||||
for (modelEntityConstant in modelEntityConstants) {
|
||||
val s = Series<Number, Number>()
|
||||
s.name = modelEntityConstant.key
|
||||
val value = modelEntityConstant.value.currentValue
|
||||
s.data.add(XYChart.Data(0.0, value))
|
||||
seriesConstants.add(s)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add values to chart series.
|
||||
*
|
||||
* @param modelEntityValues list of model entity values.
|
||||
* @param currentTime current model time.
|
||||
*/
|
||||
fun addSeriesValues(modelEntityValues: List<String>, currentTime: Double) {
|
||||
for (i in modelEntityValues.indices) {
|
||||
val valueString = modelEntityValues[i]
|
||||
val format = NumberFormat.getInstance(Locale.US)
|
||||
val number: Number
|
||||
try {
|
||||
number = format.parse(valueString)
|
||||
val value = number.toDouble()
|
||||
series[i].data?.add(XYChart.Data(currentTime, value))
|
||||
} catch (ex: ParseException) {
|
||||
Logger.getLogger(WinSimulatorApp::class.java.getName()).log(Level.SEVERE, null, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set scene width and height.
|
||||
*
|
||||
* @param width width.
|
||||
* @param height height.
|
||||
*/
|
||||
fun setSize(width: Double, height: Double) {
|
||||
Companion.width = width
|
||||
Companion.height = height
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the chart as an image.
|
||||
*
|
||||
* @param scene Scene
|
||||
*/
|
||||
private fun saveToFile(scene: Scene) {
|
||||
val image = scene.snapshot(null)
|
||||
val outputFile = File("chart.png")
|
||||
val bImage = SwingFXUtils.fromFXImage(image, null)
|
||||
try {
|
||||
ImageIO.write(bImage, "png", outputFile)
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("WinSimulatorApp started.")
|
||||
|
||||
stage.title = "WinSimulator"
|
||||
val root = BorderPane()
|
||||
|
||||
scene = Scene(root, width, height)
|
||||
|
||||
lineChart = this.createLineChart("")
|
||||
lineChart.data.addAll(series)
|
||||
lineChart.cursor = Cursor.CROSSHAIR
|
||||
lineChart.animated = false
|
||||
addTooltips()
|
||||
addLineChartContextMenu()
|
||||
|
||||
checkBoxes = ArrayList()
|
||||
for (s in series) {
|
||||
val cb = CheckBox(s.name)
|
||||
cb.onAction = EventHandler<ActionEvent> { this.updateLineChart(it) }
|
||||
cb.isSelected = true
|
||||
checkBoxes.add(cb)
|
||||
}
|
||||
|
||||
textFields = ArrayList()
|
||||
for (s in seriesConstants) {
|
||||
|
||||
val tf1 = Label(s.name)
|
||||
val tf2 = TextField(s.data[0].yValue.toString())
|
||||
val tf3 = VBox(tf1, tf2)
|
||||
|
||||
tf2.onAction = EventHandler<ActionEvent> { this.reRunSimulation(it) }
|
||||
|
||||
tf2.onKeyPressed = EventHandler<KeyEvent> {
|
||||
if (it.code == KeyCode.TAB) {
|
||||
this.reRunSimulation(it)
|
||||
}
|
||||
}
|
||||
|
||||
textFields.add(tf3)
|
||||
}
|
||||
|
||||
|
||||
val dataLabel1 = Label("Constants:")
|
||||
val vbox1 = VBox(8.0)
|
||||
vbox1.children.addAll(dataLabel1)
|
||||
vbox1.children.addAll(textFields)
|
||||
vbox1.padding = Insets(20.0, 10.0, 20.0, 10.0)
|
||||
|
||||
val dataLabel2 = Label("Entities:")
|
||||
val vbox2 = VBox(8.0)
|
||||
vbox2.children.addAll(dataLabel2)
|
||||
vbox2.children.addAll(checkBoxes)
|
||||
vbox2.padding = Insets(20.0, 10.0, 20.0, 0.0)
|
||||
|
||||
root.left = vbox1
|
||||
root.center = lineChart
|
||||
root.right = vbox2
|
||||
|
||||
stage.scene = scene
|
||||
stage.show()
|
||||
|
||||
val doOutputHandlersContainOtherJavaFxApp =
|
||||
simulation.outputHandlers.any { it.javaClass.name == "hr.unipu.ksdtoolkit.outputs.PngExporter" }
|
||||
if (doOutputHandlersContainOtherJavaFxApp) {
|
||||
simulation.outputHandlers.remove(winSimulatorListener)
|
||||
|
||||
Platform.setImplicitExit(true)
|
||||
|
||||
Platform.runLater { PngExporterApp().start(Stage()) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-run simulation (with new value of model constant).
|
||||
*
|
||||
* @param event Event
|
||||
*/
|
||||
private fun reRunSimulation(event: Event) {
|
||||
|
||||
val tf = event.source as TextField
|
||||
val text = tf.text
|
||||
val label = (tf.parent.childrenUnmodifiable[0] as Label).text
|
||||
|
||||
val isModuleEntity = label.contains('.')
|
||||
val moduleName = label.substringBefore(".")
|
||||
val entityName = label.substringAfter(".")
|
||||
|
||||
val oldValue =
|
||||
if (isModuleEntity) {
|
||||
simulation.model.modules[moduleName]?.entities?.get(entityName)?.currentValue
|
||||
} else {
|
||||
simulation.model.entities[label]?.currentValue
|
||||
}
|
||||
|
||||
val newValue = try {
|
||||
text.toDouble()
|
||||
} catch (exception: Exception) {
|
||||
LoggerFactory.getLogger(javaClass).error("WinSimulatorApp: incorrect input.", exception)
|
||||
tf.text = oldValue.toString()
|
||||
oldValue
|
||||
}
|
||||
|
||||
if (isModuleEntity) {
|
||||
simulation.model.modules[moduleName]?.entities?.get(entityName)?.equation = { newValue }
|
||||
} else {
|
||||
simulation.model.entities[label]?.equation = { newValue }
|
||||
}
|
||||
|
||||
simulation.run()
|
||||
|
||||
refreshChart()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add/remove lines from chart.
|
||||
*
|
||||
* @param event Event
|
||||
*/
|
||||
private fun updateLineChart(event: Event) {
|
||||
val checkBox = event.source as CheckBox
|
||||
val text = checkBox.text
|
||||
for (s in series) {
|
||||
if (text == s.name) {
|
||||
if (checkBox.isSelected) {
|
||||
lineChart.data.add(s)
|
||||
} else {
|
||||
lineChart.data.remove(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refresh whole chart.
|
||||
*/
|
||||
private fun refreshChart() {
|
||||
lineChart.data?.clear()
|
||||
for (s in series) {
|
||||
for (cb in checkBoxes) {
|
||||
val textCheckBox = cb.text
|
||||
if (textCheckBox == s.name) {
|
||||
if (cb.isSelected) {
|
||||
lineChart.data.add(s)
|
||||
} else {
|
||||
lineChart.data.remove(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a LineChart
|
||||
*
|
||||
* @param title line chart title
|
||||
* @return line chart
|
||||
*/
|
||||
private fun createLineChart(title: String): LineChart<Number, Number> {
|
||||
|
||||
val xAxis = NumberAxis()
|
||||
val yAxis = NumberAxis()
|
||||
xAxis.label = "Time"
|
||||
yAxis.label = "Value"
|
||||
|
||||
val lineChart = LineChart(xAxis, yAxis)
|
||||
lineChart.title = title
|
||||
|
||||
return lineChart
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a context menu to line chart.
|
||||
*/
|
||||
private fun addLineChartContextMenu() {
|
||||
val saveAsFile = MenuItem("Save as file.")
|
||||
saveAsFile.setOnAction { event -> saveToFile(scene) }
|
||||
val menu = ContextMenu(saveAsFile)
|
||||
lineChart.setOnMouseClicked { event ->
|
||||
if (MouseButton.SECONDARY == event.button) {
|
||||
menu.show(lineChart, event.screenX, event.screenY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add tooltips to chart.
|
||||
*/
|
||||
private fun addTooltips() {
|
||||
|
||||
for (i in 0 until lineChart.data.size) {
|
||||
for (j in 0 until lineChart.data[i].data.size) {
|
||||
val dot = lineChart.data[i].data[j]
|
||||
Tooltip.install(dot.node, Tooltip("Time = ${dot.xValue}\n" +
|
||||
"Value = ${dot.yValue}"))
|
||||
|
||||
dot.node.setOnMouseEntered { event -> dot.node.styleClass.add("onHover") }
|
||||
dot.node.setOnMouseExited { event -> dot.node.styleClass.remove("onHover") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package hr.unipu.ksdtoolkit.simulations
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
|
||||
interface ISimulationEventHandler {
|
||||
/**
|
||||
* Handler for a simulation initialization event.
|
||||
*/
|
||||
fun simulationInitialized(model: Model)
|
||||
|
||||
/**
|
||||
* Handler for a finished calculation for one time step.
|
||||
*/
|
||||
fun timeStepCalculated(model: Model)
|
||||
|
||||
/**
|
||||
* Handler for a finished simulation event.
|
||||
*/
|
||||
fun simulationFinished(model: Model)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package hr.unipu.ksdtoolkit.simulations
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.*
|
||||
import hr.unipu.ksdtoolkit.outputs.CsvExporter
|
||||
import hr.unipu.ksdtoolkit.outputs.PngExporter
|
||||
import hr.unipu.ksdtoolkit.outputs.WinSimulator
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
/**
|
||||
* Simulation class represents & controls SD simulation.
|
||||
*/
|
||||
class Simulation(val model: Model) {
|
||||
|
||||
val outputHandlers = ArrayList<ISimulationEventHandler>()
|
||||
|
||||
var timeElapsed = 0.0
|
||||
val timeSteps = (model.finalTime.toDouble() - model.initialTime.toDouble()) / model.timeStep.toDouble()
|
||||
|
||||
/**
|
||||
* Running simulation.
|
||||
*/
|
||||
fun run() {
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
runAllPreparations()
|
||||
while (this.finalTimeReached()) {
|
||||
runOneTimeStep()
|
||||
|
||||
|
||||
timeElapsed = (System.currentTimeMillis() - start) / 1000.0
|
||||
}
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info(" Simulation current time (finished): ${model.currentTime}")
|
||||
LoggerFactory.getLogger(javaClass).info(" TOTAL TIME ELAPSED: $timeElapsed \n")
|
||||
|
||||
this.fireSimulationFinishedEvent(model)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prepare all before while loop integration.
|
||||
*/
|
||||
fun runAllPreparations() {
|
||||
this.copyModuleEntitiesToParent()
|
||||
this.prepareInitialValues()
|
||||
this.prepareValuesForFirstTimestep()
|
||||
this.fireSimulationInitializedEvent(this.model)
|
||||
this.executeConverters()
|
||||
this.fireTimeStepCalculatedEvent(this.model)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run integration for one time step.
|
||||
*/
|
||||
fun runOneTimeStep() {
|
||||
this.updateCurrentTime()
|
||||
this.prepareValuesForNextTimestep()
|
||||
this.model.integrationType.integrate()
|
||||
this.executeConverters()
|
||||
this.fireTimeStepCalculatedEvent(this.model)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy all module entities into the parent model before simulation.
|
||||
*/
|
||||
private fun copyModuleEntitiesToParent() {
|
||||
for (( moduleName , module) in model.modules) {
|
||||
module.entities.forEach { ( modelEntityName , modelEntity) ->
|
||||
modelEntity.name = "${moduleName}.${modelEntityName}"
|
||||
if (!model.entities.containsKey(modelEntity.name))
|
||||
model.addModelEntity(modelEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare all initial model values for running the simulation.
|
||||
*/
|
||||
private fun prepareInitialValues() {
|
||||
this.model.currentTime = this.model.initialTime.toDouble()
|
||||
this.model.entities.forEach { (k, v) ->
|
||||
when (v) {
|
||||
is Stock -> {
|
||||
v.currentValue = v._initialValue?.invoke() ?: 0.0
|
||||
}
|
||||
is Constant -> {
|
||||
v.currentValue = v._equation?.invoke() ?: 0.0
|
||||
}
|
||||
}
|
||||
v.isCurrentValueCalculated = false
|
||||
}
|
||||
this.model.integrationType.stocks = model.stocks
|
||||
this.model.integrationType.converters = model.converters
|
||||
this.model.integrationType.dt = model.timeStep.toDouble()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare all Stocks whose current value is already calculated
|
||||
* for the first timestep.
|
||||
*/
|
||||
private fun prepareValuesForFirstTimestep() {
|
||||
this.model.entities.forEach { (k, v) ->
|
||||
if (v is Stock && this.model.currentTime == this.model.initialTime) {
|
||||
v.isCurrentValueCalculated = true
|
||||
}
|
||||
if (v is Constant && this.model.currentTime == this.model.initialTime) {
|
||||
v.isCurrentValueCalculated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fires an event for the initialization of the simulation.
|
||||
*
|
||||
* @param model [Model] for the simulation.
|
||||
*/
|
||||
private fun fireSimulationInitializedEvent(model: Model) {
|
||||
this.outputHandlers.forEach { listener -> listener.simulationInitialized(this.model) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the converters.
|
||||
*/
|
||||
private fun executeConverters() {
|
||||
for (converter in this.model.converters) {
|
||||
if (!converter.targetEntity.isCurrentValueCalculated) {
|
||||
converter.convert()
|
||||
}
|
||||
}
|
||||
|
||||
var hasSucceeded = false
|
||||
var numberOfTries = 0
|
||||
while (!hasSucceeded && numberOfTries<10) {
|
||||
numberOfTries++
|
||||
this.model.entities.forEach { (_, v) ->
|
||||
val invocationResult = v._equation?.invoke()
|
||||
if (v.isCurrentValueCalculated && invocationResult != null) {
|
||||
when (v) {
|
||||
is Converter -> {
|
||||
v.currentValue = invocationResult
|
||||
v.isCurrentValueCalculated = true
|
||||
|
||||
}
|
||||
is Flow -> {
|
||||
v.currentValue = invocationResult
|
||||
v.isCurrentValueCalculated = true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.model.entities.all { it.value.isCurrentValueCalculated } ) {
|
||||
hasSucceeded = true
|
||||
} else {
|
||||
|
||||
this.model.entities.filter { it.value.isCurrentValueCalculated == false }.forEach {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.model.entities.filter { !it.value.isCurrentValueCalculated }.forEach { (_, v) ->
|
||||
val invocationResult = v._equation?.invoke()
|
||||
if (!v.isCurrentValueCalculated) {
|
||||
LoggerFactory.getLogger(javaClass).info("Not yet calculated: ${v.toString()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fire an event for a finished calculation of a time step.
|
||||
*
|
||||
* @param model [Model] for the simulation.
|
||||
*/
|
||||
private fun fireTimeStepCalculatedEvent(model: Model) {
|
||||
this.outputHandlers.forEach { listener -> listener.timeStepCalculated(model) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the current time by adding one time step.
|
||||
*/
|
||||
private fun updateCurrentTime() {
|
||||
model.currentTime = model.currentTime + model.timeStep.toDouble()
|
||||
|
||||
if ( (model.currentTime / model.timeStep.toDouble()).rem(timeSteps / 10.0) == 0.0 ) {
|
||||
LoggerFactory.getLogger(javaClass).
|
||||
info(" Simulation percentage: ${model.currentTime / model.timeStep.toDouble() / timeSteps * 100}%, " +
|
||||
"Simulation current time: ${model.currentTime}")
|
||||
LoggerFactory.getLogger(javaClass).
|
||||
info(" Time elapsed: $timeElapsed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare all values for the next timestep.
|
||||
*/
|
||||
private fun prepareValuesForNextTimestep() {
|
||||
this.model.entities.forEach { (_, v) ->
|
||||
v.previousValue = v.currentValue
|
||||
when (v) {
|
||||
is Constant -> v.isCurrentValueCalculated = true
|
||||
else -> v.isCurrentValueCalculated = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method that controls if the final time has been reached.
|
||||
*
|
||||
* @return <tt>true</tt> only if the final time has been reached.
|
||||
*/
|
||||
private fun finalTimeReached(): Boolean {
|
||||
return this.model.currentTime < this.model.finalTime.toDouble()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fires an event for a finished simulation.
|
||||
*
|
||||
* @param model [Model] for the simulation.
|
||||
*/
|
||||
private fun fireSimulationFinishedEvent(model: Model) {
|
||||
this.outputHandlers.forEach { listener -> listener.simulationFinished(model) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an listener that handles simulation events (in classical API form).
|
||||
*
|
||||
* @param listener [SimulationEventListener]
|
||||
*/
|
||||
fun addSimulationEventListener(listener: ISimulationEventHandler) {
|
||||
val isListenerAlreadyAvailable = this.outputHandlers.contains(listener)
|
||||
if (!isListenerAlreadyAvailable) {
|
||||
this.outputHandlers.add(listener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a [SimulationEventListener].
|
||||
*
|
||||
* @param listener [SimulationEventListener]
|
||||
*/
|
||||
fun removeSimulationEventListener(listener: ISimulationEventHandler) {
|
||||
this.outputHandlers.remove(listener)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an listener that handles simulation events (in form of lambda expression).
|
||||
*/
|
||||
fun addSimulationEventListener(
|
||||
listenerAction: Simulation.() -> ISimulationEventHandler
|
||||
) : Unit {
|
||||
outputHandlers.add(listenerAction(this))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple listeners that handles simulation events (using lambda with receiver).
|
||||
*/
|
||||
val outputs = SimulationEventListenersHandler()
|
||||
inner class SimulationEventListenersHandler {
|
||||
fun add(listener: ISimulationEventHandler) {
|
||||
this@Simulation.outputHandlers.add(listener)
|
||||
}
|
||||
|
||||
fun CsvExporter(csvFile: String = "output.csv", separator: String = ";"): CsvExporter {
|
||||
val listener = hr.unipu.ksdtoolkit.outputs.CsvExporter(csvFile, separator)
|
||||
add(listener)
|
||||
return listener
|
||||
}
|
||||
|
||||
fun PngExporter(pngFile: String = "output.png"): PngExporter {
|
||||
PngExporter.simulation = this@Simulation
|
||||
val listener = hr.unipu.ksdtoolkit.outputs.PngExporter(pngFile)
|
||||
add(listener)
|
||||
return listener
|
||||
}
|
||||
|
||||
fun WinSimulator(): WinSimulator {
|
||||
WinSimulator.simulation = this@Simulation
|
||||
val listener = hr.unipu.ksdtoolkit.outputs.WinSimulator()
|
||||
add(listener)
|
||||
return listener
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
body: SimulationEventListenersHandler.() -> Unit) {
|
||||
body()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
11
ksdtoolkit-core/src/main/resources/Style1.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.chart-series-line{
|
||||
-fx-stroke-width: 1px;
|
||||
-fx-stroke:#eedede;
|
||||
-fx-effect: null;
|
||||
}
|
||||
.chart-vertical-grid-lines {
|
||||
-fx-stroke: #cccccc;
|
||||
}
|
||||
.chart-horizontal-grid-lines {
|
||||
-fx-stroke: #cccccc;
|
||||
}
|
||||
45
ksdtoolkit-core/src/main/resources/Style2.css
Normal file
@@ -0,0 +1,45 @@
|
||||
.chart-plot-background {
|
||||
-fx-border-color: black;
|
||||
-fx-border-width: 4px;
|
||||
/*-fx-border-insets: -2px;*/
|
||||
-fx-background: white;
|
||||
}
|
||||
|
||||
.axis:top {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:right {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:bottom {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:left {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.chart-vertical-grid-lines {
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
.chart-horizontal-grid-lines {
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
-fx-background-color: white;
|
||||
-fx-padding: 20px;
|
||||
}
|
||||
|
||||
/*.chart-major-vertical-grid-lines {*/
|
||||
/* -fx-stroke: #dddddd;*/
|
||||
/* -fx-stroke-width: 1.0;*/
|
||||
/*}*/
|
||||
|
||||
/*.chart-major-horizontal-grid-lines {*/
|
||||
/* -fx-stroke: #dddddd;*/
|
||||
/* -fx-stroke-width: 1.0;*/
|
||||
/*}*/
|
||||
21
ksdtoolkit-core/src/main/resources/Style3.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.chart {
|
||||
-fx-padding: 10px;
|
||||
-fx-background: white;
|
||||
/*-fx-background-image: url("icon.png");*/
|
||||
}
|
||||
.chart-content {
|
||||
-fx-padding: 30px;
|
||||
|
||||
}
|
||||
.chart-legend {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 20px;
|
||||
}
|
||||
|
||||
.chart-legend-item-symbol{
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
|
||||
.chart-legend-item{
|
||||
-fx-text-fill: #191970;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package hr.unipu.ksdtoolkit
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.*
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.integration.Integration
|
||||
import hr.unipu.ksdtoolkit.modules.ModuleGenericCompoundDecrease
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.CoreMatchers.instanceOf
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit testing all entities: Model, Constant, Converter, Flow, Stock, Module.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class `1_EntitiesTest` {
|
||||
val model: Model
|
||||
val CONSTANT: Constant
|
||||
val converter: Converter
|
||||
val flow: Flow
|
||||
val Stock: Stock
|
||||
val Module: ModuleGenericCompoundDecrease
|
||||
|
||||
|
||||
// Prepare model and model entities for testing.
|
||||
init {
|
||||
model = Model()
|
||||
|
||||
model.name = "Model_name"
|
||||
model.initialTime = 0.0
|
||||
model.finalTime = 100.0
|
||||
model.timeStep = 0.25
|
||||
model.integrationType = EulerIntegration()
|
||||
|
||||
CONSTANT = model.constant("CONSTANT_NAME")
|
||||
CONSTANT.equation = { 1.0 }
|
||||
|
||||
converter = model.converter("converter_name")
|
||||
converter.equation = { 2 * 1.0 }
|
||||
|
||||
flow = model.flow("flow_name")
|
||||
flow.equation = { 1 + 1.0 }
|
||||
|
||||
Stock = model.stock("Stock_name")
|
||||
Stock.initialValue = { 0.0 }
|
||||
Stock.equation = { -1.0 }
|
||||
|
||||
Module = model.createModule(
|
||||
"Module",
|
||||
"hr.unipu.ksdtoolkit.modules.ModuleGenericCompoundDecrease"
|
||||
) as ModuleGenericCompoundDecrease
|
||||
}
|
||||
|
||||
|
||||
@Test fun constantTest() {
|
||||
|
||||
// Testing that constant is created.
|
||||
assertThat(CONSTANT.name, `is`("CONSTANT_NAME"))
|
||||
|
||||
// Testing that constant value is set.
|
||||
assertThat(CONSTANT.equation?.invoke() as Double, `is`(1.0))
|
||||
}
|
||||
|
||||
|
||||
@Test fun converterTest() {
|
||||
|
||||
// Testing that converter is created.
|
||||
assertThat(converter.name, `is`("converter_name"))
|
||||
|
||||
// Testing that converter equation works.
|
||||
assertThat(converter.equation?.invoke() as Double, `is`(2.0))
|
||||
}
|
||||
|
||||
|
||||
@Test fun flowTest() {
|
||||
|
||||
// Testing that flow is created.
|
||||
assertThat(flow.name, `is`("flow_name"))
|
||||
|
||||
// Testing that flow equation works.
|
||||
assertThat(flow.equation?.invoke() as Double, `is`(2.0))
|
||||
}
|
||||
|
||||
|
||||
// Abstract method ModelEntity is tested using implementation of concrete classes.
|
||||
/*
|
||||
@Test fun modelEntityTest() { }
|
||||
*/
|
||||
|
||||
|
||||
@Test fun stockTest() {
|
||||
|
||||
// Testing that Stock is created.
|
||||
assertThat(Stock.name, `is`("Stock_name"))
|
||||
|
||||
// Testing Stock initial value works.
|
||||
assertThat(Stock.initialValue?.invoke() as Double, `is`(0.0))
|
||||
|
||||
// Testing Stock equation works.
|
||||
assertThat(Stock.equation?.invoke() as Double, `is`(-1.0))
|
||||
}
|
||||
|
||||
|
||||
@Test fun moduleTest() {
|
||||
|
||||
// Testing that Module is created.
|
||||
assertThat(Module.name, `is`("Module"))
|
||||
|
||||
// Test that all Module entities can be accessed.
|
||||
assertThat(Module.entities.keys, `is`(setOf("CONSTANT", "INITIAL_STOCK", "Stock",
|
||||
"inflow", "outflow", "converter" )))
|
||||
|
||||
// Testing equation in Module works.
|
||||
assertThat(Module.INITIAL_STOCK.equation?.invoke() as Double, `is`(100.0))
|
||||
}
|
||||
|
||||
|
||||
@Test fun modelTest() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model.name, `is`("Model_name"))
|
||||
|
||||
// Testing that setup works correctly.
|
||||
assertThat(model.initialTime.toDouble(), `is`(0.0))
|
||||
assertThat(model.finalTime.toDouble(), `is`(100.0))
|
||||
assertThat(model.timeStep.toDouble(), `is`(0.25))
|
||||
|
||||
// Testing that setting integration type works correctly.
|
||||
assertThat(model.integrationType, instanceOf(EulerIntegration::class.java))
|
||||
|
||||
// Testing that all entities are in the model.
|
||||
assertThat(model.entities, `is`(hashMapOf("CONSTANT_NAME" to CONSTANT,
|
||||
"converter_name" to converter,
|
||||
"flow_name" to flow,
|
||||
"Stock_name" to Stock)))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package hr.unipu.ksdtoolkit
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.entities.Stock
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.integration.RungeKuttaIntegration
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit testing numeric integrations: Euler type and RungeKutta type.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class `2_IntegrationsTest` {
|
||||
|
||||
private val model: Model
|
||||
private val Stock: Stock
|
||||
private val simulation: Simulation
|
||||
|
||||
// Prepare model and model entities for testing.
|
||||
init {
|
||||
model = Model()
|
||||
model.initialTime = 0.0
|
||||
model.finalTime = 100.0
|
||||
model.timeStep = 0.25
|
||||
|
||||
Stock = model.stock("Stock_name")
|
||||
Stock.initialValue = { 0.0 }
|
||||
Stock.equation = { -1.0 }
|
||||
|
||||
simulation = Simulation(model)
|
||||
}
|
||||
|
||||
|
||||
@Test fun EulerItegrationTest() {
|
||||
|
||||
model.integrationType = EulerIntegration()
|
||||
simulation.runAllPreparations()
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(-0.25))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(-0.50))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(-0.75))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(-1.00))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test fun RugneKuttaIntegrationTest() {
|
||||
|
||||
model.integrationType = RungeKuttaIntegration()
|
||||
simulation.runAllPreparations()
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(closeTo(-0.25, 0.01)))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(closeTo(-0.50, 0.01)))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(closeTo(-0.75, 0.01)))
|
||||
|
||||
simulation.runOneTimeStep()
|
||||
assertThat(Stock.currentValue as Double, `is`(closeTo(-1.00, 0.01)))
|
||||
}
|
||||
|
||||
|
||||
// Abstract method Integration is tested using implementation of concrete classes.
|
||||
/*
|
||||
@Test fun ``2_IntegrationsTest``() { }
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package hr.unipu.ksdtoolkit
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.integration.EulerIntegration
|
||||
import hr.unipu.ksdtoolkit.models.*
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import hr.unipu.ksdtoolkit.entities.*
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit testing models:
|
||||
* - GenericSD,
|
||||
* - SimpleCompoundInterest, InheritedCompoundInterest,
|
||||
* - BassDiffusion, InnovationDiffusion,
|
||||
* - TestSpeed model.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class `3_ModelsTest` {
|
||||
|
||||
private val model1: Model
|
||||
private val model2: Model
|
||||
private val model3: Model
|
||||
private val model4: Model
|
||||
private val model5: Model
|
||||
private val model6: Model
|
||||
|
||||
init {
|
||||
model1 = ModelGenericSD()
|
||||
model2 = ModelSimpleCompoundInterest()
|
||||
model3 = ModelInheritedCompoundInterest()
|
||||
model4 = ModelBassDiffusion()
|
||||
model5 = ModelInnovationDiffusion()
|
||||
model6 = ModelTestSpeed()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun model1Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model1.name, `is`("Generic SD Model"))
|
||||
|
||||
// Testing that setup works correctly.
|
||||
assertThat(model1.initialTime.toDouble(), `is`(0.0))
|
||||
assertThat(model1.finalTime.toDouble(), `is`(120.0))
|
||||
assertThat(model1.timeStep.toDouble(), `is`(0.25))
|
||||
|
||||
// Testing that setting integration type works correctly.
|
||||
assertThat(model1.integrationType, instanceOf(EulerIntegration::class.java))
|
||||
|
||||
// Testing Stock initial value works.
|
||||
assertThat((model1.entities["Stock"] as Stock)._initialValue?.invoke(), `is`(100.0))
|
||||
|
||||
// Testing that all entities are in the model.
|
||||
assertThat(
|
||||
model1.entities.keys, `is`(
|
||||
hashMapOf(
|
||||
"CONSTANT" to Constant("CONSTANT"),
|
||||
"INITIAL_STOCK" to Constant("INITIAL_STOCK"),
|
||||
"converter" to Converter("converter"),
|
||||
"inflow" to Flow("inflow"),
|
||||
"outflow" to Flow("outflow"),
|
||||
"Stock" to Stock("Stock")
|
||||
).keys
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(
|
||||
model1.entities.keys, `is`(
|
||||
setOf(
|
||||
"CONSTANT",
|
||||
"INITIAL_STOCK",
|
||||
"converter",
|
||||
"inflow",
|
||||
"outflow",
|
||||
"Stock"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Testing equation in model works.
|
||||
assertThat( (((model1.entities["Stock"] as Stock)
|
||||
.initialValue?.invoke() as ModelEntity)
|
||||
.equation?.invoke() as Int).toDouble(), `is`(closeTo(100.0, 0.001)))
|
||||
|
||||
// Testing that Module is created.
|
||||
assertThat(model1.modules["Module"]?.name, `is`("Module"))
|
||||
|
||||
// Test that all Module entities can be accessed.
|
||||
assertThat(model1.modules["Module"]?.entities?.keys, `is`(setOf("CONSTANT", "INITIAL_STOCK", "Stock",
|
||||
"inflow", "outflow", "converter" )))
|
||||
|
||||
// Testing equation in Module works.
|
||||
assertThat( (model1.modules["Module"]?.entities!!["Stock"] as Stock)._initialValue?.invoke(),
|
||||
`is`(100.0))
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun model2Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model2.name, `is`("Simple Compound Interest Model"))
|
||||
|
||||
// Other assertions are similar to the first test.
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun model3Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model3.name, `is`("Inherited Compound Interest Model"))
|
||||
|
||||
// Other assertions are similar to the first test.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun model4Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model4.name, `is`("Bass Diffusion Model"))
|
||||
|
||||
// Other assertions are similar to the first test.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun model5Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model5.name, `is`("Innovation/Product Diffusion Model"))
|
||||
|
||||
// Other assertions are similar to the first test.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun model6Test() {
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(model6.name, `is`("SD Model for testing speed"))
|
||||
|
||||
// Other assertions are similar to the first test.
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package hr.unipu.ksdtoolkit
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.models.*
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.slf4j.LoggerFactory
|
||||
import hr.unipu.ksdtoolkit.models.ModelTestSpeed as ModelTestSpeed
|
||||
|
||||
|
||||
/**
|
||||
* Unit testing simulation run for all models:
|
||||
* - GenericSD,
|
||||
* - SimpleCompoundInterest, InheritedCompoundInterest,
|
||||
* - BassDiffusion, InnovationDiffusion,
|
||||
* - TestSpeed model.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class `4_SimulationRunTest` {
|
||||
|
||||
private var myModel: Model
|
||||
private val myModel1: Model
|
||||
private val myModel2: Model
|
||||
private val myModel3: Model
|
||||
private val myModel4: Model
|
||||
private val myModel5: Model
|
||||
private val myModel6: Model
|
||||
|
||||
private var mySimulation: Simulation
|
||||
private val mySimulation1: Simulation
|
||||
private val mySimulation2: Simulation
|
||||
private val mySimulation3: Simulation
|
||||
private val mySimulation4: Simulation
|
||||
private val mySimulation5: Simulation
|
||||
private val mySimulation6: Simulation
|
||||
|
||||
|
||||
init {
|
||||
// 1-3. Create the model
|
||||
myModel1 = ModelGenericSD()
|
||||
myModel2 = ModelSimpleCompoundInterest()
|
||||
myModel3 = ModelInheritedCompoundInterest()
|
||||
myModel4 = ModelBassDiffusion()
|
||||
myModel5 = ModelInnovationDiffusion()
|
||||
myModel6 = ModelTestSpeed()
|
||||
|
||||
|
||||
// 4. Create the simulation
|
||||
mySimulation1 = Simulation(myModel1)
|
||||
mySimulation2 = Simulation(myModel2)
|
||||
mySimulation3 = Simulation(myModel3)
|
||||
mySimulation4 = Simulation(myModel4)
|
||||
mySimulation5 = Simulation(myModel5)
|
||||
mySimulation6 = Simulation(myModel6)
|
||||
|
||||
|
||||
// select the model & simulation for the output
|
||||
myModel = myModel1
|
||||
mySimulation = mySimulation1
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation1RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation.
|
||||
assertThat(mySimulation1.model.name, `is`("Generic SD Model"))
|
||||
|
||||
|
||||
// Testing that setup works correctly.
|
||||
assertThat(mySimulation1.model.initialTime.toDouble(), `is`(0.0))
|
||||
assertThat(mySimulation1.model.finalTime.toDouble(), `is`(120.0))
|
||||
assertThat(mySimulation1.model.timeStep.toDouble(), `is`(0.25))
|
||||
|
||||
|
||||
// Testing model & simulation at time=0 (beginning of simulation).
|
||||
LoggerFactory.getLogger(javaClass).info("Running Simulation 0: at final time=0")
|
||||
myModel1.finalTime = 0
|
||||
mySimulation1.run()
|
||||
assertThat(myModel1.entities["INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Module.CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
|
||||
|
||||
// Testing model & simulation at time=60 (middle of simulation).
|
||||
LoggerFactory.getLogger(javaClass).info("Running Simulation 1: at final time=60")
|
||||
myModel1.finalTime = 60
|
||||
mySimulation1.run()
|
||||
assertThat(myModel1.entities["INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Module.CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
|
||||
|
||||
|
||||
// Testing model & simulation at time=120 (end of simulation).
|
||||
LoggerFactory.getLogger(javaClass).info("Running Simulation 2: at final time=120")
|
||||
myModel1.finalTime = 120
|
||||
mySimulation1.run()
|
||||
assertThat(myModel1.entities["INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.inflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.outflow"]?.currentValue, `is`(closeTo(0.8333, 0.001)))
|
||||
assertThat(myModel1.entities["Module.Stock"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.converter"]?.currentValue, `is`(closeTo(0.0083, 0.001)))
|
||||
assertThat(myModel1.entities["Module.CONSTANT"]?.currentValue, `is`(closeTo(10.0, 0.001)))
|
||||
assertThat(myModel1.entities["Module.INITIAL_STOCK"]?.currentValue, `is`(closeTo(100.0, 0.001)))
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation2RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation
|
||||
assertThat(mySimulation2.model.name, `is`("Simple Compound Interest Model"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation3RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation
|
||||
assertThat(mySimulation3.model.name, `is`("Inherited Compound Interest Model"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation4RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation
|
||||
assertThat(mySimulation4.model.name, `is`("Bass Diffusion Model"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation5RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation
|
||||
assertThat(mySimulation5.model.name, `is`("Innovation/Product Diffusion Model"))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun simulation6RunTest() {
|
||||
|
||||
// Testing that model is passed to simulation
|
||||
assertThat(mySimulation6.model.name, `is`("SD Model for testing speed"))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package hr.unipu.ksdtoolkit
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.models.*
|
||||
import hr.unipu.ksdtoolkit.outputs.*
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.junit.Test
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.junit.Assert.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Unit testing simulation outputs:
|
||||
* - CsvExporter
|
||||
* - PngExporter
|
||||
* - WinSimulator
|
||||
* - WebSimulator
|
||||
* - MobSimulator
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class `5_SimulationOutputsTest` {
|
||||
|
||||
private var model: Model
|
||||
private var simulation: Simulation
|
||||
private lateinit var csvExporter: CsvExporter // ISimulationEventHandler needed for tests.
|
||||
private lateinit var pngExporter: PngExporter // ISimulationEventHandler needed for tests.
|
||||
private lateinit var winSimulator: WinSimulator // ISimulationEventHandler needed for tests.
|
||||
|
||||
init {
|
||||
|
||||
// 1-3. Create the model
|
||||
model = ModelGenericSD()
|
||||
//model = ModelSimpleCompoundInterest()
|
||||
//model = ModelInheritedCompoundInterest()
|
||||
//model = ModelBassDiffusion()
|
||||
//model = ModelInnovationDiffusion()
|
||||
//model = ModelTestSpeed()
|
||||
|
||||
|
||||
// 4. Create the simulation
|
||||
simulation = Simulation(model)
|
||||
|
||||
|
||||
// 5. Add results handlers
|
||||
simulation.outputs {
|
||||
csvExporter = CsvExporter("output_data.csv", ";")
|
||||
pngExporter = PngExporter("output_chart.png")
|
||||
winSimulator = WinSimulator()
|
||||
|
||||
// !!! Mobile simulator test has to be run from Android Test, because Java modules
|
||||
// cannot depend on Android modules (reverse is ok).
|
||||
//MobSimulator()
|
||||
|
||||
//WebSimulator()
|
||||
}
|
||||
|
||||
// 6. Run the simulation
|
||||
simulation.run()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test fun csvExporterTest() {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("\n---CsvExporterTest---")
|
||||
|
||||
// Testing that sb is created.
|
||||
assertThat(csvExporter.sb, instanceOf(StringBuilder::class.java))
|
||||
|
||||
|
||||
// Testing that sb contains all simulation data keys.
|
||||
val entities = simulation.model.modelEntitiesKeys
|
||||
for (entity in entities) {
|
||||
assertThat(csvExporter.sb.toString(), containsString(entity))
|
||||
}
|
||||
|
||||
// Testing that sb contains all simulation data values.
|
||||
val values = simulation.model.modelEntitiesValues
|
||||
for (value in values) {
|
||||
assertThat(csvExporter.sb.toString(), containsString(value))
|
||||
}
|
||||
|
||||
// Testing that CSV file is saved.
|
||||
assertTrue(File("output_data.csv").exists());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test fun pngExporterTest() {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("\n---PngExporterTest---")
|
||||
|
||||
// Testing that PngExporterApp is initialized.
|
||||
assertThat(PngExporterApp.fileName, `is`("output_chart.png"))
|
||||
|
||||
// Testing that PngExporterApp contains all data series.
|
||||
val entities = simulation.model.modelEntitiesKeys
|
||||
for (entity in entities) {
|
||||
assertThat(PngExporterApp.series.toString(), containsString(entity))
|
||||
}
|
||||
|
||||
// Testing that PngExporterApp contains all simulation data values.
|
||||
val values = simulation.model.modelEntitiesValues
|
||||
for ((index,value) in values.withIndex()) {
|
||||
// Quick & dirty to replace decimal point format from "," to "."
|
||||
assertThat(PngExporterApp.series[index].data[0].yValue.toString().replace(",",".").toDouble(),
|
||||
`is`(closeTo(value.replace(",",".").toDouble(), 0.001)))
|
||||
}
|
||||
|
||||
// Testing that PNG file is saved.
|
||||
assertTrue(File("output_chart.png").exists());
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test fun winSimulatorTest() {
|
||||
LoggerFactory.getLogger(javaClass).info("\n---WinSimulatorTest---")
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
// !!! Mobile simulator test has to be run from Android Test.
|
||||
@Test fun MobSimulatorTest() {
|
||||
LoggerFactory.getLogger(javaClass).info("---MobSimulatorTest---")
|
||||
}
|
||||
|
||||
|
||||
@Test fun WebSimulatorTest() {
|
||||
LoggerFactory.getLogger(javaClass).info("---WebSimulatorTest---")
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
93
ksdtoolkit-mobapp/build.gradle.kts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Subproject: "ksdtoolkit-mobapp".
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
plugins {
|
||||
|
||||
id("com.android.application")
|
||||
|
||||
/**
|
||||
* !!! Problems if "kotlin-android" and "kotlin-android-extensions" are included with plugin "com.android.application"
|
||||
* - SOLUTION: Use full names e.g. "org.jetbrains.kotlin.android.extensions", so that
|
||||
* pluginManagement in settings.gradle.kts can recognize name.
|
||||
*/
|
||||
id("org.jetbrains.kotlin.android")
|
||||
|
||||
id("org.jetbrains.kotlin.android.extensions")
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion(28)
|
||||
buildToolsVersion("29.0.3")
|
||||
defaultConfig {
|
||||
applicationId = "hr.unipu.mobilesimulatorapp"
|
||||
minSdkVersion(24)
|
||||
targetSdkVersion(28)
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
testBuildType = "debug"
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
setSourceCompatibility(JavaVersion.VERSION_1_8)
|
||||
setTargetCompatibility(JavaVersion.VERSION_1_8)
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(path = ":ksdtoolkit-core", configuration = "default"))
|
||||
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
|
||||
api("com.google.android.material:material:1.3.0-alpha04")
|
||||
|
||||
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.20")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.20")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.3.2")
|
||||
implementation("androidx.appcompat:appcompat:1.2.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
|
||||
|
||||
testImplementation("junit:junit:4.13.1")
|
||||
testImplementation("org.mockito:mockito-core:3.1.0")
|
||||
testImplementation("androidx.test:core:1.3.0")
|
||||
|
||||
androidTestImplementation("androidx.test:core:1.3.0")
|
||||
androidTestImplementation("androidx.test:core-ktx:1.3.0")
|
||||
androidTestImplementation("androidx.test:runner:1.3.0")
|
||||
androidTestImplementation("androidx.test:rules:1.3.0")
|
||||
androidTestImplementation("org.hamcrest:hamcrest-library:1.3")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.2")
|
||||
androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||
}
|
||||
|
||||
|
||||
tasks.register("appStart") {
|
||||
dependsOn(":ksdtoolkit-mobapp:installDebug")
|
||||
doLast {
|
||||
exec {
|
||||
commandLine("cmd", "/c", "adb", "shell", "am", "start", "-n", "hr.unipu.mobilesimulatorapp/.MobSimulatorApp")
|
||||
}
|
||||
}
|
||||
}
|
||||
21
ksdtoolkit-mobapp/gradle.properties
Normal file
@@ -0,0 +1,21 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
@@ -0,0 +1,106 @@
|
||||
package hr.unipu.mobilesimulatorapp
|
||||
|
||||
import android.app.PendingIntent.getActivity
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.launchActivity
|
||||
import androidx.test.ext.junit.rules.activityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.models.ModelGenericSD
|
||||
import hr.unipu.ksdtoolkit.models.ModelInnovationDiffusion
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers
|
||||
import org.junit.*
|
||||
import org.junit.runner.RunWith
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device or emulator.
|
||||
* - These tests have access to Instrumentation APIs, give you access to information such as the Context of the app
|
||||
* you are testing, and let you control the app under test from your test code.
|
||||
* - The Gradle build interprets these test source sets in the same manner as it does for your project's app source
|
||||
* sets, which allows you to create tests based on build variants.
|
||||
* - More info: https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests
|
||||
*/
|
||||
|
||||
@RunWith(AndroidJUnit4::class) // AndroidJUnit4 as default test runner.
|
||||
class MobSimulatorInstrumentedTest {
|
||||
|
||||
@After fun launchApp() {
|
||||
launchActivity<MobSimulatorApp>()
|
||||
Thread.sleep(30000) // Automatic Testing closes app after tests are finished.
|
||||
// Manual testing needs this pause for postponing the app closing.
|
||||
}
|
||||
|
||||
private val model: Model
|
||||
private var simulation: Simulation
|
||||
|
||||
init {
|
||||
// Create generic model.
|
||||
//model = ModelGenericSD()
|
||||
model = ModelInnovationDiffusion()
|
||||
|
||||
// Create the simulation.
|
||||
simulation = Simulation(model)
|
||||
|
||||
// Add results handlers.
|
||||
MobSimulatorApp.simulation = simulation // Add simulation (and model) objects to app companion.
|
||||
simulation.addSimulationEventListener(MobSimulator())
|
||||
|
||||
// Run the simulation
|
||||
simulation.run()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Testing that simulation results are accessible from the mobile app.
|
||||
*/
|
||||
@Test fun mobSimulatorTest() {
|
||||
|
||||
// Initial test that testing framework is working.
|
||||
// - Testing that context of the app under test is correct.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertThat(appContext.packageName, containsString("hr.unipu.mobilesimulatorapp"))
|
||||
|
||||
|
||||
// Testing that model is accessible from the mobile app.
|
||||
Assert.assertNotNull(model.name)
|
||||
LoggerFactory.getLogger(javaClass).info("\n---modelTest---")
|
||||
|
||||
|
||||
// Testing that simulation is accessible from the mobile app.
|
||||
// - Testing that model is passed to simulation.
|
||||
Assert.assertNotNull(MobSimulatorApp.simulation.model.name)
|
||||
LoggerFactory.getLogger(javaClass).info("\n---simulationTest---")
|
||||
|
||||
|
||||
// Testing that MobSimulatorApp contains all data series.
|
||||
val entities = MobSimulatorApp.simulation.model.modelEntitiesKeys
|
||||
for (entity in entities) {
|
||||
Assert.assertThat(MobSimulatorApp.seriesName.toString(), containsString(entity))
|
||||
}
|
||||
LoggerFactory.getLogger(javaClass).info("---mobSimulatorTest---")
|
||||
|
||||
|
||||
// Testing that MobSimulatorApp contains all simulation data values.
|
||||
val values = simulation.model.modelEntitiesValues
|
||||
for ((index, value) in values.withIndex()) {
|
||||
Assert.assertThat(
|
||||
MobSimulatorApp.seriesValue[index][0].y.toString().replace(",", ".").toDouble(),
|
||||
`is`(Matchers.closeTo(value.replace(",", ".").toDouble(), 0.001))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
19
ksdtoolkit-mobapp/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="hr.unipu.mobilesimulatorapp">
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.MyApp">
|
||||
<activity android:name=".MobSimulatorApp">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,27 @@
|
||||
package hr.unipu.mobilesimulatorapp
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Constant
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
|
||||
/**
|
||||
* Class that implements the [SimulationEventListener] interface and controls the chart printing on Android.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class MobSimulator : ISimulationEventHandler {
|
||||
|
||||
override fun simulationInitialized(model: Model) {
|
||||
MobSimulatorApp.addConstants(model.entities.filterValues { it is Constant })
|
||||
MobSimulatorApp.addSeriesNames(model.modelEntitiesKeys)
|
||||
}
|
||||
|
||||
override fun timeStepCalculated(model: Model) {
|
||||
MobSimulatorApp.addSeriesValues(model.modelEntitiesValues, model.currentTime)
|
||||
}
|
||||
|
||||
override fun simulationFinished(model: Model) {
|
||||
MobSimulatorApp.simulationFinished = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package hr.unipu.mobilesimulatorapp
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.mikephil.charting.charts.LineChart
|
||||
import com.github.mikephil.charting.components.YAxis
|
||||
import com.github.mikephil.charting.data.Entry
|
||||
import com.github.mikephil.charting.data.LineData
|
||||
import com.github.mikephil.charting.data.LineDataSet
|
||||
import com.github.mikephil.charting.highlight.Highlight
|
||||
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
|
||||
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
|
||||
import hr.unipu.ksdtoolkit.entities.ModelEntity
|
||||
import hr.unipu.ksdtoolkit.models.ModelInnovationDiffusion
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Android mobile simulator app - for chart plotting and interactive simulation.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class MobSimulatorApp : AppCompatActivity() {
|
||||
|
||||
init {
|
||||
|
||||
/**---Creating: model, simulation and run.---**/
|
||||
|
||||
// 1. Create a demo model.
|
||||
val model = ModelInnovationDiffusion() // Or other models: ModelGenericSD(), ModelTestSpeed(), etc.
|
||||
|
||||
// 2. Create the simulation.
|
||||
simulation = Simulation(model)
|
||||
|
||||
// 3. Add results handlers
|
||||
simulation.addSimulationEventListener(MobSimulator())
|
||||
|
||||
// 4. Run simulation (so that all expressions are invoked for time=0).
|
||||
simulation.run()
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
public lateinit var simulation: Simulation
|
||||
|
||||
public var simulationFinished: Boolean = false
|
||||
public var seriesName: ArrayList<String> = ArrayList()
|
||||
public var seriesValue: ArrayList<ArrayList<Entry>> = ArrayList()
|
||||
public var constantsName: ArrayList<String> = ArrayList()
|
||||
public var constantsValue: ArrayList<Entry> = ArrayList()
|
||||
public var time: ArrayList<Double> = ArrayList()
|
||||
public val lineDataSets: ArrayList<LineDataSet> = ArrayList()
|
||||
|
||||
fun addSeriesNames(modelEntityNames: List<String>) {
|
||||
seriesName = ArrayList()
|
||||
seriesValue = ArrayList()
|
||||
for (modelEntityName in modelEntityNames) {
|
||||
seriesName.add(modelEntityName)
|
||||
seriesValue.add(ArrayList<Entry>())
|
||||
lineDataSets.add(LineDataSet(null, ""))
|
||||
}
|
||||
}
|
||||
|
||||
fun addSeriesValues(modelEntityValues: List<String>, currentTime: Double) {
|
||||
time.add(currentTime)
|
||||
for (i in 0..modelEntityValues.indices.last) {
|
||||
val valueString = modelEntityValues[i]
|
||||
val value = valueString.toFloat()
|
||||
val entry = Entry(currentTime.toFloat(), value)
|
||||
seriesValue[i].add(entry)
|
||||
}
|
||||
}
|
||||
|
||||
fun addConstants(modelEntityConstants: Map<String, ModelEntity>) {
|
||||
constantsName = ArrayList()
|
||||
constantsValue = ArrayList()
|
||||
for (modelEntityConstant in modelEntityConstants) {
|
||||
constantsName.add(modelEntityConstant.key)
|
||||
val value = modelEntityConstant.value.currentValue.toFloat()
|
||||
val entry = Entry(0f, value)
|
||||
constantsValue.add(entry)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("---onCreate() invoked.---")
|
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
|
||||
drawUI()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creating layout programmatically (dynamically).
|
||||
*/
|
||||
private fun drawUI() {
|
||||
LoggerFactory.getLogger(javaClass).info("---drawUI() invoked.---")
|
||||
|
||||
val rootLayout = ScrollView(this)
|
||||
rootLayout.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
val linearLayoutHorizontal = LinearLayout(this)
|
||||
val linearLayoutHorizontalParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
linearLayoutHorizontal.orientation = LinearLayout.HORIZONTAL
|
||||
rootLayout.addView(linearLayoutHorizontal, linearLayoutHorizontalParams)
|
||||
|
||||
val linearLayoutVertical = LinearLayout(this)
|
||||
|
||||
linearLayoutVertical.id = R.id.insertPoint
|
||||
linearLayoutVertical.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
linearLayoutVertical.orientation = LinearLayout.VERTICAL
|
||||
linearLayoutHorizontal.addView(linearLayoutVertical)
|
||||
|
||||
for (s in MobSimulatorApp.constantsName) {
|
||||
val textView = TextView(this)
|
||||
textView.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
textView.text = s
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10F)
|
||||
linearLayoutVertical.addView(textView)
|
||||
|
||||
val editText = EditText(this)
|
||||
editText.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
editText.setEms(5)
|
||||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||
val indexOfEntity = simulation.model.modelEntitiesKeys.indexOf(s)
|
||||
editText.setText(simulation.model.modelEntitiesValues[indexOfEntity])
|
||||
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10F)
|
||||
editText.setOnFocusChangeListener(object : View.OnFocusChangeListener {
|
||||
override fun onFocusChange(v: View?, hasFocus: Boolean) {
|
||||
if (!hasFocus) {
|
||||
val newValue = editText.text.toString().toDouble()
|
||||
val modelVariableName = textView.text
|
||||
simulation.model.entities[modelVariableName]?.equation = { newValue }
|
||||
refreshUI()
|
||||
}
|
||||
}
|
||||
})
|
||||
linearLayoutVertical.addView(editText)
|
||||
}
|
||||
|
||||
val lineChart = LineChart(this)
|
||||
|
||||
val myColorsPalette = intArrayOf(
|
||||
Color.rgb(217, 80, 138),
|
||||
Color.rgb(254, 149, 7),
|
||||
Color.rgb(254, 247, 120),
|
||||
Color.rgb(106, 167, 134),
|
||||
Color.rgb(53, 194, 209),
|
||||
Color.rgb(64, 89, 128),
|
||||
Color.rgb(149, 165, 124),
|
||||
Color.rgb(217, 184, 162),
|
||||
Color.rgb(191, 134, 134),
|
||||
Color.rgb(179, 48, 80)
|
||||
)
|
||||
val numberOfColorInPalette = myColorsPalette.size
|
||||
|
||||
for (i in seriesValue.indices) {
|
||||
lineDataSets[i] = LineDataSet(seriesValue[i], seriesName[i])
|
||||
lineDataSets[i].color = myColorsPalette[i.rem(numberOfColorInPalette)]
|
||||
lineDataSets[i].circleHoleColor = myColorsPalette[i.rem(numberOfColorInPalette)]
|
||||
lineDataSets[i].setCircleColor(myColorsPalette[i.rem(numberOfColorInPalette)])
|
||||
lineDataSets[i].setDrawValues(false)
|
||||
lineDataSets[i].setAxisDependency(YAxis.AxisDependency.LEFT)
|
||||
}
|
||||
|
||||
lineChart.data = LineData(lineDataSets as List<ILineDataSet>?)
|
||||
|
||||
lineChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
|
||||
override fun onValueSelected(e: Entry?, h: Highlight?) {
|
||||
fun Float.format(digits: Int) = "%.${digits}f".format(this)
|
||||
val tooltipText = "x=${e?.x?.format(2)}, y=${e?.y?.format(2)}"
|
||||
LoggerFactory.getLogger(javaClass).info("---Value selected: $tooltipText---")
|
||||
Toast.makeText(this@MobSimulatorApp, tooltipText, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
override fun onNothingSelected() { }
|
||||
})
|
||||
|
||||
lineChart.layoutParams = ViewGroup.LayoutParams(
|
||||
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250F, this.resources.displayMetrics).toInt()
|
||||
)
|
||||
lineChart.id = R.id.lineChart
|
||||
linearLayoutHorizontal.addView(lineChart)
|
||||
|
||||
setContentView(rootLayout);
|
||||
}
|
||||
|
||||
|
||||
private fun runSimulation() {
|
||||
LoggerFactory.getLogger(javaClass).info("---runSimulation() invoked.---")
|
||||
|
||||
if (MobSimulatorApp.simulationFinished == false) {
|
||||
val doesNotContainMobSimulatorListener = simulation.outputHandlers.filterIsInstance(MobSimulator()::class.java).isEmpty()
|
||||
if (doesNotContainMobSimulatorListener) {
|
||||
|
||||
simulation.addSimulationEventListener(MobSimulator())
|
||||
}
|
||||
|
||||
simulation.run()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun refreshUI() {
|
||||
LoggerFactory.getLogger(javaClass).info("---refreshUI() invoked.---")
|
||||
|
||||
MobSimulatorApp.simulationFinished = false
|
||||
runSimulation()
|
||||
|
||||
drawUI()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
11
ksdtoolkit-mobapp/src/main/java/resources/Style1.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.chart-series-line{
|
||||
-fx-stroke-width: 1px;
|
||||
-fx-stroke:#eedede;
|
||||
-fx-effect: null;
|
||||
}
|
||||
.chart-vertical-grid-lines {
|
||||
-fx-stroke: #cccccc;
|
||||
}
|
||||
.chart-horizontal-grid-lines {
|
||||
-fx-stroke: #cccccc;
|
||||
}
|
||||
45
ksdtoolkit-mobapp/src/main/java/resources/Style2.css
Normal file
@@ -0,0 +1,45 @@
|
||||
.chart-plot-background {
|
||||
-fx-border-color: black;
|
||||
-fx-border-width: 4px;
|
||||
/*-fx-border-insets: -2px;*/
|
||||
-fx-background: white;
|
||||
}
|
||||
|
||||
.axis:top {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:right {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:bottom {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.axis:left {
|
||||
-fx-border-color: transparent;
|
||||
}
|
||||
|
||||
.chart-vertical-grid-lines {
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
.chart-horizontal-grid-lines {
|
||||
-fx-stroke: transparent;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
-fx-background-color: white;
|
||||
-fx-padding: 20px;
|
||||
}
|
||||
|
||||
/*.chart-major-vertical-grid-lines {*/
|
||||
/* -fx-stroke: #dddddd;*/
|
||||
/* -fx-stroke-width: 1.0;*/
|
||||
/*}*/
|
||||
|
||||
/*.chart-major-horizontal-grid-lines {*/
|
||||
/* -fx-stroke: #dddddd;*/
|
||||
/* -fx-stroke-width: 1.0;*/
|
||||
/*}*/
|
||||
21
ksdtoolkit-mobapp/src/main/java/resources/Style3.css
Normal file
@@ -0,0 +1,21 @@
|
||||
.chart {
|
||||
-fx-padding: 10px;
|
||||
-fx-background: white;
|
||||
/*-fx-background-image: url("icon.png");*/
|
||||
}
|
||||
.chart-content {
|
||||
-fx-padding: 30px;
|
||||
|
||||
}
|
||||
.chart-legend {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 20px;
|
||||
}
|
||||
|
||||
.chart-legend-item-symbol{
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
|
||||
.chart-legend-item{
|
||||
-fx-text-fill: #191970;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
115
ksdtoolkit-mobapp/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--<androidx.constraintlayout.widget.ConstraintLayout-->
|
||||
<!-- xmlns:android="http://schemas.android.com/apk/res/android"-->
|
||||
<!-- xmlns:app="http://schemas.android.com/apk/res-auto"-->
|
||||
<!-- xmlns:tools="http://schemas.android.com/tools"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- tools:context=".MobViewerApp">-->
|
||||
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MobSimulatorApp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/insert_point"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
||||
<!-- style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"-->
|
||||
<!-- android:layout_width="125dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:hint="CONTACT_RATE"-->
|
||||
<!-- android:textSize="10sp">-->
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="0.01"-->
|
||||
<!-- android:textSize="10sp"/>-->
|
||||
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CONTACT_RATE"
|
||||
android:textSize="10sp"></TextView>
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="5"
|
||||
android:inputType="textPersonName"
|
||||
android:text="100"
|
||||
android:textSize="10sp"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ADVERTISING_EFFECTIVENESS"
|
||||
android:textSize="10sp"></TextView>
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="5"
|
||||
android:inputType="textPersonName"
|
||||
android:text="0.011"
|
||||
android:textSize="10sp"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TOTAL_POPULATION"
|
||||
android:textSize="10sp"></TextView>
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="5"
|
||||
android:inputType="textPersonName"
|
||||
android:text="10000"
|
||||
android:textSize="10sp"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ADOPTION_FRACTION"
|
||||
android:textSize="10sp"></TextView>
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="5"
|
||||
android:inputType="textPersonName"
|
||||
android:text="0.015"
|
||||
android:textSize="10sp"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.github.mikephil.charting.charts.LineChart
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/line_chart" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Hello World!"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintLeft_toLeftOf="parent"-->
|
||||
<!-- app:layout_constraintRight_toRightOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent" />-->
|
||||
|
||||
<!--</androidx.constraintlayout.widget.ConstraintLayout>-->
|
||||
51
ksdtoolkit-mobapp/src/main/res/layout/my_view.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--<androidx.constraintlayout.widget.ConstraintLayout-->
|
||||
<!-- xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="match_parent">-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MobSimulatorApp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/constantName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CONSTANT"
|
||||
android:textSize="10sp">
|
||||
</TextView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/constantValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="5"
|
||||
android:inputType="textPersonName"
|
||||
android:text="0.0"
|
||||
android:textSize="10sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputLayout-->
|
||||
<!-- xmlns:android="http://schemas.android.com/apk/res/android"-->
|
||||
<!-- xmlns:tools="http://schemas.android.com/tools"-->
|
||||
<!-- android:layout_width="125dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:id="@+id/textInputLayout"-->
|
||||
<!-- android:textSize="6sp"-->
|
||||
<!-- tools:ignore="MissingConstraints">-->
|
||||
|
||||
<!-- <com.google.android.material.textfield.TextInputEditText-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:textSize="12sp"-->
|
||||
<!-- android:id="@+id/textInputEditText" />-->
|
||||
<!-- </com.google.android.material.textfield.TextInputLayout>-->
|
||||
|
||||
<!--</androidx.constraintlayout.widget.ConstraintLayout>-->
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
ksdtoolkit-mobapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 16 KiB |
6
ksdtoolkit-mobapp/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
</resources>
|
||||
5
ksdtoolkit-mobapp/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="activity_horizontal_margin">5px</dimen>
|
||||
<dimen name="activity_vertical_margin">5px</dimen>
|
||||
</resources>
|
||||
5
ksdtoolkit-mobapp/src/main/res/values/ids.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="insertPoint"/>
|
||||
<item type="id" name="lineChart"/>
|
||||
</resources>
|
||||
4
ksdtoolkit-mobapp/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">MobSimulator</string>
|
||||
<string name="input">input</string>
|
||||
</resources>
|
||||
36
ksdtoolkit-mobapp/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
|
||||
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
|
||||
<!-- <!– Customize your theme here. –>-->
|
||||
<!-- <item name="colorPrimary">@color/colorPrimary</item>-->
|
||||
<!-- <item name="colorPrimaryDark">@color/colorPrimaryDark</item>-->
|
||||
<!-- <item name="colorAccent">@color/colorAccent</item>-->
|
||||
<!-- </style>-->
|
||||
|
||||
<style name="Theme.MyApp" parent="Theme.MaterialComponents.Light">
|
||||
|
||||
<item name="textAppearanceHeadline1">@style/TextAppearance.MaterialComponents.Headline1</item>
|
||||
<item name="textAppearanceHeadline2">@style/TextAppearance.MaterialComponents.Headline2</item>
|
||||
<item name="textAppearanceHeadline3">@style/TextAppearance.MaterialComponents.Headline3</item>
|
||||
<item name="textAppearanceHeadline4">@style/TextAppearance.MaterialComponents.Headline4</item>
|
||||
<item name="textAppearanceHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||
<item name="textAppearanceHeadline6">@style/TextAppearance.MaterialComponents.Headline6</item>
|
||||
<item name="textAppearanceSubtitle1">@style/TextAppearance.MaterialComponents.Subtitle1</item>
|
||||
<item name="textAppearanceSubtitle2">@style/TextAppearance.MaterialComponents.Subtitle2</item>
|
||||
<item name="textAppearanceBody1">@style/TextAppearance.MaterialComponents.Body1</item>
|
||||
<item name="textAppearanceBody2">@style/TextAppearance.MaterialComponents.Body2</item>
|
||||
<item name="textAppearanceCaption">@style/TextAppearance.MaterialComponents.Caption</item>
|
||||
<item name="textAppearanceButton">@style/TextAppearance.MaterialComponents.Button</item>
|
||||
<item name="textAppearanceOverline">@style/TextAppearance.MaterialComponents.Overline</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearence.App.TextInputLayout" parent="@android:style/TextAppearance">
|
||||
<item name="android:textSize">6sp</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,60 @@
|
||||
package hr.unipu.mobilesimulatorapp
|
||||
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.models.*
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Local unit test, which will execute on local JVM.
|
||||
* - Use these tests to minimize execution time when your tests have no Android framework dependencies or
|
||||
* when you can mock the Android framework dependencies.
|
||||
* - More info: https://developer.android.com/training/testing/unit-testing/local-unit-tests
|
||||
*/
|
||||
class MobSimulatorLocalUnitTest {
|
||||
|
||||
private val model: Model
|
||||
private var simulation: Simulation
|
||||
|
||||
init {
|
||||
// Create generic model.
|
||||
model = ModelGenericSD()
|
||||
|
||||
// Create the simulation.
|
||||
simulation = Simulation(model)
|
||||
|
||||
// Add results handlers.
|
||||
MobSimulatorApp.simulation = simulation // Add simulation (and model) objects to app companion.
|
||||
simulation.addSimulationEventListener(MobSimulator())
|
||||
|
||||
// Run the simulation
|
||||
//simulation.run()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Testing that model is accessible from mobile app.
|
||||
@Test fun modelTest() {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("\n---modelTest---")
|
||||
|
||||
// Testing that model is created.
|
||||
assertThat(MobSimulatorApp.simulation.model.name, CoreMatchers.`is`("Generic SD Model"))
|
||||
}
|
||||
|
||||
|
||||
// Testing that simulation is accessible from mobile app.
|
||||
@Test fun simulationTest() {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("\n---simulationTest---")
|
||||
|
||||
// Testing that model is passed to simulation.
|
||||
assertThat(MobSimulatorApp.simulation.model.name, CoreMatchers.`is`("Generic SD Model"))
|
||||
}
|
||||
|
||||
}
|
||||
160
ksdtoolkit-webapp/build.gradle.kts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Subproject: "ksdtoolkit-webapp".
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
|
||||
id("org.gretty") version "3.0.3"
|
||||
|
||||
id("com.devsoap.plugin.vaadin") version "2.0.0.beta2"
|
||||
}
|
||||
|
||||
defaultTasks("clean", "build")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven(url = "http://maven.vaadin.com/vaadin-addons" ) // vaadin-addons
|
||||
}
|
||||
|
||||
|
||||
tasks.withType<KotlinCompile>().all {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
vaadin {
|
||||
version = "8.5.2"
|
||||
}
|
||||
|
||||
gretty {
|
||||
contextPath = "/"
|
||||
servletContainer = "jetty9.4"
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
}
|
||||
|
||||
val staging by configurations.creating
|
||||
|
||||
dependencies {
|
||||
implementation(project(path = ":ksdtoolkit-core", configuration = "default"))
|
||||
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
|
||||
implementation("org.apache.poi:poi:3.15")
|
||||
|
||||
implementation("com.github.mvysny.karibudsl:karibu-dsl-v8:1.0.3")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
|
||||
implementation("org.slf4j:slf4j-simple:1.7.30")
|
||||
|
||||
implementation("org.slf4j:jul-to-slf4j:1.7.30")
|
||||
|
||||
testImplementation("com.github.mvysny.kaributesting:karibu-testing-v8:1.2.6")
|
||||
testImplementation("com.github.mvysny.dynatest:dynatest-engine:0.19")
|
||||
|
||||
implementation("com.vaadin:vaadin-themes:${vaadin.version}")
|
||||
implementation("com.vaadin:vaadin-client-compiled:${vaadin.version}")
|
||||
|
||||
implementation("com.vaadin:vaadin-push:${vaadin.version}")
|
||||
implementation("com.vaadin:vaadin-server:${vaadin.version}")
|
||||
implementation("javax.servlet:javax.servlet-api:4.0.1")
|
||||
|
||||
implementation("com.vaadin:vaadin-charts:4.0.5")
|
||||
implementation("com.vaadin:vaadin-spreadsheet:2.0.1")
|
||||
|
||||
implementation("org.vaadin.addons:dcharts-widget:1.7.0")
|
||||
|
||||
runtimeOnly("com.google.gwt:gwt-servlet:2.8.2")
|
||||
|
||||
staging("com.heroku:webapp-runner-main:9.0.36.1")
|
||||
}
|
||||
|
||||
|
||||
tasks {
|
||||
val copyToLib by registering(Copy::class) {
|
||||
into("$buildDir/server")
|
||||
from(staging) {
|
||||
include("webapp-runner*")
|
||||
}
|
||||
}
|
||||
val stage by registering {
|
||||
dependsOn("build", copyToLib)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
tasks.withType<com.devsoap.plugin.tasks.CompileWidgetsetTask> {
|
||||
doFirst {
|
||||
setJvmArgs("-Dvaadin.spreadsheet.developer.license=\${.vaadin.charts.developer.license}",
|
||||
"-Dvaadin.charts.developer.license=\${.vaadin.spreadsheet.developer.license}")
|
||||
}
|
||||
}
|
||||
|
||||
val compileKotlin: KotlinCompile by tasks
|
||||
compileKotlin.kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
val compileTestKotlin: KotlinCompile by tasks
|
||||
compileTestKotlin.kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* How to add breakpoint for debugging in Gradle+gretty?
|
||||
* - Start the gretty plug-in, appStartDebug, run directly (normal mode), without debugging.
|
||||
* - Set up remote debugging. Add Remote, click on configuration.
|
||||
* The configuration port is synchronized with the project gretty configuration: debugPort=5005
|
||||
* Select the corresponding module.
|
||||
* - Perform debugging.
|
||||
* Start the web project first (in Normal mode), then start the remote just configured (in Debug mode),
|
||||
* and then complete gretty remote debugging.
|
||||
* - Project can be started from Terminal as well:
|
||||
* e.g. gradlew tasks -Dorg.gradle.debug=true --no-daemon
|
||||
* - To stop all Gradle daemons:
|
||||
* e.g. gradlew --stop
|
||||
* - Run 'gradle appStop' to stop the server.
|
||||
*/
|
||||
|
||||
gretty {
|
||||
jvmArgs = mutableListOf("-Xmx1024m", "-XX:MaxPermSize=512m")
|
||||
servletContainer = "jetty9.4"
|
||||
contextPath = "/"
|
||||
scanInterval = 0
|
||||
inplaceMode = "hard"
|
||||
debugPort = 5005
|
||||
debugSuspend = true
|
||||
}
|
||||
4
ksdtoolkit-webapp/gradle.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
vaadin.charts.developer.license=c0171602-2326-457a-837d-226c949709f1
|
||||
vaadin.spreadsheet.developer.license=8bf94b9d-b891-47b0-bad3-971445a274a5
|
||||
systemProp.vaadin.charts.developer.license=c0171602-2326-457a-837d-226c949709f1
|
||||
systemProp.vaadin.spreadsheet.developer.license=8bf94b9d-b891-47b0-bad3-971445a274a5
|
||||
@@ -0,0 +1,28 @@
|
||||
package hr.unipu.websimulatorapp
|
||||
|
||||
import hr.unipu.ksdtoolkit.entities.Constant
|
||||
import hr.unipu.ksdtoolkit.entities.Model
|
||||
import hr.unipu.ksdtoolkit.simulations.ISimulationEventHandler
|
||||
import hr.unipu.websimulatorapp.webapp.WebSimulatorApp
|
||||
|
||||
/**
|
||||
* Class that implements the [SimulationEventListener] interface and controls the chart printing on web.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
class WebSimulator : ISimulationEventHandler {
|
||||
|
||||
override fun simulationInitialized(model: Model) {
|
||||
WebSimulatorApp.addConstants(model.entities.filterValues { it is Constant })
|
||||
WebSimulatorApp.addSeriesNames(model.modelEntitiesKeys)
|
||||
|
||||
}
|
||||
|
||||
override fun timeStepCalculated(model: Model) {
|
||||
WebSimulatorApp.addSeriesValues(model.modelEntitiesValues, model.currentTime)
|
||||
}
|
||||
|
||||
override fun simulationFinished(model: Model) {
|
||||
WebSimulatorApp.simulationFinished = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package hr.unipu.websimulatorapp.webapp
|
||||
|
||||
import com.vaadin.ui.*
|
||||
import com.vaadin.ui.themes.ValoTheme
|
||||
|
||||
class CustomLabelTextField : CustomField<String>() {
|
||||
val textField = TextField()
|
||||
private val labelText = Label()
|
||||
var modelVariableName = ""
|
||||
|
||||
override fun initContent(): Component {
|
||||
|
||||
labelText.styleName = ValoTheme.LABEL_SMALL
|
||||
labelText.setWidth("200px")
|
||||
|
||||
textField.styleName = ValoTheme.TEXTFIELD_SMALL
|
||||
textField.setWidth("100px")
|
||||
|
||||
|
||||
val myLayout = HorizontalLayout()
|
||||
myLayout.addComponents(labelText)
|
||||
myLayout.addComponent(textField)
|
||||
myLayout.setComponentAlignment(labelText, Alignment.MIDDLE_LEFT)
|
||||
myLayout.setComponentAlignment(textField, Alignment.MIDDLE_RIGHT)
|
||||
myLayout.styleName = ValoTheme.LAYOUT_COMPONENT_GROUP
|
||||
|
||||
return myLayout
|
||||
}
|
||||
|
||||
override fun getValue(): String {
|
||||
return textField.value
|
||||
}
|
||||
|
||||
override fun setValue(value: String) {
|
||||
this.textField.value = value
|
||||
}
|
||||
|
||||
override fun getCaption(): String {
|
||||
return labelText.caption
|
||||
}
|
||||
|
||||
override fun setCaption(caption: String) {
|
||||
this.labelText.caption = caption
|
||||
}
|
||||
|
||||
override fun doSetValue(s: String) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package hr.unipu.websimulatorapp.webapp
|
||||
|
||||
import com.vaadin.server.Sizeable
|
||||
import com.vaadin.ui.*
|
||||
import com.vaadin.ui.themes.ValoTheme
|
||||
|
||||
class CustomSliderLabel : CustomField<String>() {
|
||||
private val sliderCaption = Label()
|
||||
val slider = Slider()
|
||||
private val sliderValue = Label()
|
||||
var modelVariableName = ""
|
||||
|
||||
override fun initContent(): Component {
|
||||
|
||||
sliderCaption.styleName = ValoTheme.LABEL_SMALL
|
||||
sliderCaption.setWidth("200px")
|
||||
|
||||
slider.styleName = ValoTheme.TEXTFIELD_SMALL
|
||||
slider.setWidth(100f, Sizeable.Unit.PERCENTAGE)
|
||||
|
||||
sliderValue.styleName = ValoTheme.LABEL_SMALL
|
||||
sliderValue.setWidth("100px")
|
||||
|
||||
val myLayout = HorizontalLayout()
|
||||
myLayout.addComponent(sliderCaption)
|
||||
myLayout.addComponent(slider)
|
||||
myLayout.addComponent(sliderValue)
|
||||
myLayout.setComponentAlignment(sliderCaption, Alignment.MIDDLE_LEFT)
|
||||
myLayout.setComponentAlignment(slider, Alignment.MIDDLE_LEFT)
|
||||
myLayout.setComponentAlignment(sliderValue, Alignment.MIDDLE_RIGHT)
|
||||
myLayout.styleName = ValoTheme.LAYOUT_COMPONENT_GROUP
|
||||
|
||||
return myLayout
|
||||
}
|
||||
|
||||
override fun getValue(): String {
|
||||
return sliderValue.value
|
||||
}
|
||||
|
||||
override fun setValue(value: String) {
|
||||
this.sliderValue.value = value
|
||||
}
|
||||
|
||||
fun setValue(value: Double?) {
|
||||
this.slider.value = value!!
|
||||
}
|
||||
|
||||
override fun getCaption(): String {
|
||||
return sliderCaption.value
|
||||
}
|
||||
|
||||
override fun setCaption(value: String) {
|
||||
this.sliderCaption.value = value
|
||||
}
|
||||
|
||||
override fun doSetValue(s: String) {
|
||||
|
||||
}
|
||||
|
||||
fun setResolution(resolution: Int) {
|
||||
this.slider.resolution = resolution
|
||||
}
|
||||
|
||||
fun setMin(resolution: Double) {
|
||||
this.slider.min = resolution
|
||||
}
|
||||
|
||||
fun setMax(resolution: Double) {
|
||||
this.slider.max = resolution
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package hr.unipu.websimulatorapp.webapp
|
||||
|
||||
import com.vaadin.server.FontAwesome
|
||||
import com.vaadin.server.Sizeable
|
||||
import com.vaadin.ui.*
|
||||
import com.vaadin.ui.themes.ValoTheme
|
||||
|
||||
class CustomTextFieldButton : CustomField<String>() {
|
||||
private val textField = TextField()
|
||||
|
||||
override fun initContent(): Component {
|
||||
|
||||
textField.setWidth(100f, Sizeable.Unit.PERCENTAGE)
|
||||
textField.styleName = ValoTheme.TEXTFIELD_SMALL
|
||||
textField.styleName = ValoTheme.TEXTFIELD_BORDERLESS
|
||||
textField.isReadOnly = true
|
||||
|
||||
val button = Button()
|
||||
button.addStyleName(ValoTheme.BUTTON_ICON_ONLY)
|
||||
button.icon = FontAwesome.USER
|
||||
|
||||
val myLayout = CssLayout()
|
||||
myLayout.addComponents(textField, button)
|
||||
myLayout.styleName = ValoTheme.LAYOUT_COMPONENT_GROUP
|
||||
return myLayout
|
||||
}
|
||||
|
||||
override fun getValue(): String {
|
||||
return textField.value
|
||||
}
|
||||
|
||||
override fun setValue(value: String) {
|
||||
this.textField.value = value
|
||||
}
|
||||
|
||||
override fun doSetValue(s: String) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package hr.unipu.websimulatorapp.webapp
|
||||
|
||||
import com.vaadin.server.Sizeable
|
||||
import com.vaadin.ui.FormLayout
|
||||
import com.vaadin.ui.TextField
|
||||
import com.vaadin.ui.Window
|
||||
|
||||
|
||||
class CustomWindowSettings @JvmOverloads constructor(caption: String = "Simulation Settings") : Window(caption) {
|
||||
|
||||
init {
|
||||
this.setWidth(400.0f, Sizeable.Unit.PIXELS)
|
||||
|
||||
val form = FormLayout()
|
||||
|
||||
val tf1 = TextField("Initial time:")
|
||||
tf1.value = WebSimulatorApp.simulation.model.initialTime.toString()
|
||||
tf1.addValueChangeListener { valueChangeEvent -> WebSimulatorApp.simulation.model.initialTime = java.lang.Double.parseDouble(valueChangeEvent.value) }
|
||||
form.addComponent(tf1)
|
||||
|
||||
val tf2 = TextField("Final time:")
|
||||
tf2.value = WebSimulatorApp.simulation.model.finalTime.toString()
|
||||
tf2.addValueChangeListener { valueChangeEvent -> WebSimulatorApp.simulation.model.finalTime = java.lang.Double.parseDouble(valueChangeEvent.value) }
|
||||
form.addComponent(tf2)
|
||||
|
||||
val tf3 = TextField("Time step:")
|
||||
tf3.value = WebSimulatorApp.simulation.model.timeStep.toString()
|
||||
tf3.addValueChangeListener { valueChangeEvent -> WebSimulatorApp.simulation.model.timeStep = java.lang.Double.parseDouble(valueChangeEvent.value) }
|
||||
form.addComponent(tf3)
|
||||
|
||||
|
||||
this.addCloseListener { closeEvent ->
|
||||
WebSimulatorApp.simulationFinished == false
|
||||
}
|
||||
|
||||
form.setMargin(true)
|
||||
this.content = form
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
package hr.unipu.websimulatorapp.webapp
|
||||
|
||||
|
||||
import com.vaadin.addon.charts.Chart
|
||||
import com.vaadin.addon.charts.model.*
|
||||
import com.vaadin.addon.spreadsheet.Spreadsheet
|
||||
import com.vaadin.annotations.PreserveOnRefresh
|
||||
import com.vaadin.annotations.Theme
|
||||
import com.vaadin.annotations.VaadinServletConfiguration
|
||||
import com.vaadin.external.org.slf4j.LoggerFactory
|
||||
import com.vaadin.server.*
|
||||
import com.vaadin.shared.ui.ValueChangeMode
|
||||
import com.vaadin.ui.*
|
||||
import com.vaadin.ui.Label
|
||||
import com.vaadin.ui.themes.ValoTheme
|
||||
import hr.unipu.ksdtoolkit.entities.ModelEntity
|
||||
import hr.unipu.ksdtoolkit.models.*
|
||||
import hr.unipu.ksdtoolkit.simulations.Simulation
|
||||
import hr.unipu.websimulatorapp.WebSimulator
|
||||
import org.apache.poi.ss.usermodel.Cell
|
||||
import java.text.ParseException
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
import javax.servlet.annotation.WebServlet
|
||||
|
||||
/**
|
||||
* Vaadin web simulator app - for chart plotting and interactive simulation.
|
||||
*
|
||||
* @author [Siniša Sovilj](mailto:sinisa.sovilj@unipu.hr)
|
||||
*/
|
||||
|
||||
|
||||
@Theme("mytheme")
|
||||
@PreserveOnRefresh
|
||||
class WebSimulatorApp : UI() {
|
||||
|
||||
init {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("---WebSimulatorApp init invoked.---")
|
||||
|
||||
/**---Creating: model, simulation and run.---**/
|
||||
|
||||
// 1. Create a model.
|
||||
val model = ModelInnovationDiffusion()
|
||||
//val model = ModelGenericSD()
|
||||
//val model = ModelSimpleCompoundInterest()
|
||||
//val model = ModelInheritedCompoundInterest()
|
||||
//val model = ModelBassDiffusion()
|
||||
//val model = ModelTestSpeed()
|
||||
|
||||
// 2. Create the simulation.
|
||||
simulation = Simulation(model)
|
||||
|
||||
// 3. Add results handlers
|
||||
simulation.addSimulationEventListener(WebSimulator())
|
||||
|
||||
// 4. Run simulation (so that all expressions are invoked for time=0).
|
||||
simulation.run()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class member variables.
|
||||
*/
|
||||
private var timeUnit = simulation.model.timeUnit
|
||||
|
||||
|
||||
/**
|
||||
* Static variables and methods.
|
||||
*/
|
||||
companion object {
|
||||
|
||||
private val LAYOUT_WIDTH_COLUMN1 = "450px"
|
||||
private val LAYOUT_WIDTH_COLUMN2 = "450px"
|
||||
private val LAYOUT_WIDTH_COLUMN3 = "600px"
|
||||
private val CHART_WIDTH: String = "450px"
|
||||
private val CHART_HEIGHT: String = "350px"
|
||||
private val DATA_IN_ROWS = true
|
||||
|
||||
private var seriesNames: ArrayList<String> = ArrayList()
|
||||
private var seriesUnits: ArrayList<String> = ArrayList()
|
||||
private var seriesValues: ArrayList<ListSeries> = ArrayList()
|
||||
private var time: ArrayList<Double> = ArrayList()
|
||||
private var constantsName: ArrayList<String> = ArrayList()
|
||||
private var constantsValue: ArrayList<DataSeriesItem> = ArrayList()
|
||||
|
||||
public lateinit var simulation: Simulation
|
||||
public var simulationFinished: Boolean = false
|
||||
|
||||
|
||||
/**
|
||||
* Add series names to web simulator (for chart series).
|
||||
*
|
||||
* @param modelEntityNames list of model entity names.
|
||||
*/
|
||||
fun addSeriesNames(modelEntityNames: List<String>) {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("---SeriesNames added.---")
|
||||
|
||||
seriesNames = ArrayList()
|
||||
seriesValues = ArrayList()
|
||||
seriesUnits = ArrayList()
|
||||
time = ArrayList()
|
||||
val modelEntityUnits = simulation.model.modelEntitiesUnits
|
||||
for (modelEntityName in modelEntityNames) {
|
||||
val index = modelEntityNames.indexOf(modelEntityName)
|
||||
val modelEntityUnit = modelEntityUnits[index]
|
||||
seriesNames.add(modelEntityName)
|
||||
seriesUnits.add(modelEntityUnit)
|
||||
val series = ListSeries()
|
||||
series.name = modelEntityName
|
||||
seriesValues.add(series)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add constants to web simulator.
|
||||
*
|
||||
* @param modelEntityConstants list of model constants.
|
||||
*/
|
||||
fun addConstants(modelEntityConstants: Map<String, ModelEntity>) {
|
||||
|
||||
constantsName = ArrayList()
|
||||
constantsValue = ArrayList()
|
||||
|
||||
for (modelEntityConstant in modelEntityConstants) {
|
||||
constantsName.add(modelEntityConstant.key)
|
||||
val value = modelEntityConstant.value.currentValue
|
||||
val item = DataSeriesItem(0, value)
|
||||
constantsValue.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add series values to web simulator (for chart series).
|
||||
*
|
||||
* @param modelEntityValues list of myModel entity values.
|
||||
* @param currentTime current myModel time.
|
||||
*/
|
||||
fun addSeriesValues(modelEntityValues: List<String>, currentTime: Double) {
|
||||
time.add(currentTime)
|
||||
for (i in 0..modelEntityValues.indices.last) {
|
||||
val valueString = modelEntityValues[i]
|
||||
try {
|
||||
val value = valueString.toDouble()
|
||||
seriesValues[i].addData(value)
|
||||
} catch (exception: ParseException) {
|
||||
Logger.getLogger(WebSimulatorApp::class.java.name).log(
|
||||
Level.SEVERE, "Parsing entity values from model unsuccessful.", exception)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Vaadin Servlet.
|
||||
*/
|
||||
@WebServlet(urlPatterns = ["/*"], name = "MyUIServlet", asyncSupported = true)
|
||||
@VaadinServletConfiguration(ui = WebSimulatorApp::class, productionMode = false)
|
||||
class MyUIServlet : VaadinServlet()
|
||||
|
||||
|
||||
/**
|
||||
* Vaadin init() method - entry point in the web application.
|
||||
*/
|
||||
override fun init(vaadinRequest: VaadinRequest) {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("---Vaadin 'init' method invoked.---")
|
||||
|
||||
runSimulation()
|
||||
|
||||
drawUI()
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draw UI - for drawing and re-drawing.
|
||||
*/
|
||||
private fun drawUI() {
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("---'draw' method invoked.---")
|
||||
|
||||
val contentVertical = VerticalLayout()
|
||||
val contentHorizontal = HorizontalLayout()
|
||||
val layoutGrid = GridLayout(3, 2)
|
||||
|
||||
val header = Label("WebSimulator: ${simulation.model.name}")
|
||||
header.styleName = ValoTheme.LABEL_H2
|
||||
contentVertical.addComponent(header)
|
||||
contentVertical.setComponentAlignment(header, Alignment.TOP_CENTER)
|
||||
contentVertical.addComponent(layoutGrid)
|
||||
contentHorizontal.addComponent(contentVertical)
|
||||
content = contentHorizontal
|
||||
|
||||
|
||||
val layoutInput = createInputSection()
|
||||
layoutGrid.addComponent(layoutInput, 0, 0)
|
||||
|
||||
val layoutModel = createBackendSection()
|
||||
layoutGrid.addComponent(layoutModel, 1, 0)
|
||||
|
||||
val layoutOutput1 = createChartSection()
|
||||
layoutGrid.addComponent(layoutOutput1, 2, 0)
|
||||
|
||||
val layoutControl = createControlSection()
|
||||
layoutGrid.addComponent(layoutControl, 0, 1)
|
||||
|
||||
var layoutOutput2 = VerticalLayout()
|
||||
|
||||
if (false) layoutOutput2 = createGridSection()
|
||||
if (true) layoutOutput2 = createSpreadsheetSection()
|
||||
|
||||
layoutGrid.addComponent(layoutOutput2, 2, 1)
|
||||
}
|
||||
|
||||
|
||||
private fun runSimulation() {
|
||||
|
||||
if (WebSimulatorApp.simulationFinished == false) {
|
||||
val doesNotContainWebSimulatorListener = simulation.outputHandlers.filterIsInstance(WebSimulator::class.java).isEmpty()
|
||||
if (doesNotContainWebSimulatorListener) {
|
||||
|
||||
simulation.addSimulationEventListener(WebSimulator())
|
||||
}
|
||||
LoggerFactory.getLogger(javaClass).info("---Simulation (re)-run()---")
|
||||
simulation.run()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-run simulation & refresh UI.
|
||||
*/
|
||||
private fun refreshUI() {
|
||||
WebSimulatorApp.simulationFinished = false
|
||||
runSimulation()
|
||||
drawUI()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create Input section.
|
||||
*/
|
||||
private fun createInputSection(): VerticalLayout {
|
||||
val layoutInput = VerticalLayout()
|
||||
layoutInput.setWidth(LAYOUT_WIDTH_COLUMN1)
|
||||
|
||||
val lblInput = Label("INPUT (parameters):")
|
||||
lblInput.setHeight("24px")
|
||||
lblInput.styleName = ValoTheme.LABEL_COLORED
|
||||
layoutInput.addComponent(lblInput)
|
||||
|
||||
for (s in WebSimulatorApp.constantsName) {
|
||||
|
||||
val labelTextField = CustomLabelTextField()
|
||||
labelTextField.caption = s
|
||||
val indexOfEntity = simulation.model.modelEntitiesKeys.indexOf(s)
|
||||
labelTextField.value = simulation.model.modelEntitiesValues[indexOfEntity]
|
||||
labelTextField.modelVariableName = simulation.model.modelEntitiesKeys[indexOfEntity]
|
||||
labelTextField.textField.valueChangeMode =
|
||||
ValueChangeMode.LAZY
|
||||
labelTextField.textField.valueChangeTimeout = 1600
|
||||
labelTextField.textField.addValueChangeListener { valueChangeEvent ->
|
||||
|
||||
LoggerFactory.getLogger(javaClass).info("Value changed in: $valueChangeEvent")
|
||||
|
||||
val newValue = valueChangeEvent.value.toDouble()
|
||||
Notification.show("Value changed:", newValue.toString(), Notification.Type.TRAY_NOTIFICATION);
|
||||
|
||||
val modelVariableName = labelTextField.modelVariableName
|
||||
simulation.model.entities[modelVariableName]?.equation = { newValue }
|
||||
refreshUI()
|
||||
}
|
||||
layoutInput.addComponent(labelTextField)
|
||||
}
|
||||
|
||||
return layoutInput
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create Control section.
|
||||
*/
|
||||
private fun createControlSection(): VerticalLayout {
|
||||
val layoutControl = VerticalLayout()
|
||||
|
||||
val lblControl = Label("CONTROL (simulation):")
|
||||
lblControl.setHeight("24px")
|
||||
lblControl.styleName = ValoTheme.LABEL_COLORED
|
||||
|
||||
val buttonControl = Button("Simulation Settings")
|
||||
val window = CustomWindowSettings("Simulation Settings")
|
||||
buttonControl.addClickListener { clickEvent ->
|
||||
this.ui.ui.addWindow(window)
|
||||
}
|
||||
window.addCloseListener {
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
val buttonRun = Button("Run Simulation")
|
||||
buttonRun.addClickListener { e ->
|
||||
refreshUI()
|
||||
}
|
||||
layoutControl.addComponent(lblControl)
|
||||
layoutControl.addComponent(buttonControl)
|
||||
layoutControl.addComponents(buttonRun)
|
||||
return layoutControl
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create image of the SD model.
|
||||
*/
|
||||
private fun createBackendSection(): VerticalLayout {
|
||||
val layoutModel = VerticalLayout()
|
||||
layoutModel.setWidth(LAYOUT_WIDTH_COLUMN2)
|
||||
|
||||
val lblModel = Label("MODEL (backend):")
|
||||
lblModel.setHeight("24px")
|
||||
lblModel.styleName = ValoTheme.LABEL_COLORED
|
||||
|
||||
val imageFileName0 = "/" + "NoModelImage.png"
|
||||
val imageFileName1 = "/" + simulation.model.javaClass.name.substringAfterLast(".") + ".png"
|
||||
val imageFileName2 = "/" + simulation.model.javaClass.name.substringAfterLast(".") + ".jpg"
|
||||
val sc = VaadinServlet.getCurrent().servletContext
|
||||
val path0 = sc.getRealPath(imageFileName0)
|
||||
val path1 = sc.getRealPath(imageFileName1)
|
||||
val path2 = sc.getRealPath(imageFileName2)
|
||||
|
||||
val loader: ClassLoader = this::class.java.getClassLoader()
|
||||
val url0 = loader.getResource(imageFileName0)
|
||||
val url1 = loader.getResource(imageFileName1)
|
||||
val url2 = loader.getResource(imageFileName2)
|
||||
|
||||
val path = if (url1 != null) {
|
||||
path1
|
||||
} else {
|
||||
if (url2 != null) {
|
||||
path2
|
||||
} else {
|
||||
path0
|
||||
}
|
||||
}
|
||||
|
||||
val resource = ClassResource("/" + path.substringAfterLast("""\"""))
|
||||
val image = Image(null, resource)
|
||||
|
||||
image.setSizeFull()
|
||||
|
||||
layoutModel.addComponent(lblModel)
|
||||
layoutModel.addComponent(image)
|
||||
|
||||
return layoutModel
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create output Chart section.
|
||||
*/
|
||||
private fun createChartSection(): VerticalLayout {
|
||||
val layoutOutput1 = VerticalLayout()
|
||||
layoutOutput1.setWidth(LAYOUT_WIDTH_COLUMN3)
|
||||
|
||||
val lblOutput1 = Label("OUTPUT (graph):")
|
||||
lblOutput1.setHeight("24px")
|
||||
lblOutput1.styleName = ValoTheme.LABEL_COLORED
|
||||
|
||||
val chart = Chart()
|
||||
|
||||
val configuration = chart.configuration
|
||||
configuration.chart.type = ChartType.LINE
|
||||
configuration.title.text = ""
|
||||
configuration.subTitle.text = ""
|
||||
|
||||
val yAxis = configuration.getyAxis()
|
||||
yAxis.setTitle("Value")
|
||||
|
||||
val xAxis = configuration.getxAxis()
|
||||
xAxis.setTitle("Time [$timeUnit]")
|
||||
|
||||
val legend = configuration.legend
|
||||
legend.layout = LayoutDirection.VERTICAL
|
||||
legend.align = HorizontalAlign.RIGHT
|
||||
legend.verticalAlign = VerticalAlign.TOP
|
||||
legend.borderWidth = 0
|
||||
|
||||
val startTime = time[0]
|
||||
val endTime = time[time.size - 1]
|
||||
val intervalTime = time[1] - time[0]
|
||||
val plotOptions = PlotOptionsLine()
|
||||
plotOptions.pointStart = startTime
|
||||
plotOptions.pointInterval = intervalTime
|
||||
configuration.setPlotOptions(plotOptions)
|
||||
|
||||
if (seriesNames.maxOf { it.length } > 25) {
|
||||
legend.enabled = false
|
||||
}
|
||||
|
||||
for (i in 0 until Companion.seriesValues.size) {
|
||||
configuration.addSeries(Companion.seriesValues[i])
|
||||
}
|
||||
chart.drawChart(configuration)
|
||||
|
||||
layoutOutput1.addComponent(lblOutput1)
|
||||
layoutOutput1.addComponent(chart)
|
||||
|
||||
return layoutOutput1
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create output Grid section.
|
||||
*/
|
||||
private fun createGridSection(): VerticalLayout {
|
||||
|
||||
val layoutOutput2 = VerticalLayout()
|
||||
|
||||
val lblOutput2 = Label("OUTPUT (grid):")
|
||||
lblOutput2.setHeight("24px")
|
||||
lblOutput2.styleName = ValoTheme.LABEL_COLORED
|
||||
|
||||
val dataRowsSize = Companion.seriesValues.size
|
||||
|
||||
val rowNames = mutableListOf("Time" + " [${simulation.model.timeUnit}]")
|
||||
for (index in 0 until dataRowsSize) {
|
||||
rowNames.add(seriesValues[index].name + " [${seriesUnits[index]}]")
|
||||
}
|
||||
|
||||
val rows = ArrayList<LinkedHashMap<String, String>>(rowNames.size * Companion.seriesValues[0].data.size)
|
||||
var series1: Array<Number>
|
||||
var series2: ArrayList<Double>
|
||||
for (i in 0..time.indices.last) {
|
||||
val fakeBean = LinkedHashMap<String, String>()
|
||||
for (j in 0..rowNames.indices.last) {
|
||||
if (j > 0) {
|
||||
series1 = (Companion.seriesValues[j - 1].data)
|
||||
fakeBean[rowNames[j]] = series1[i].toString()
|
||||
} else {
|
||||
series2 = time
|
||||
fakeBean[rowNames[j]] = series2[i].toString()
|
||||
}
|
||||
|
||||
}
|
||||
rows.add(fakeBean)
|
||||
}
|
||||
|
||||
val grid = Grid<LinkedHashMap<String, String>>()
|
||||
grid.setItems(rows)
|
||||
grid.setSizeFull()
|
||||
|
||||
val s = rows[0]
|
||||
for (entry in s.entries) {
|
||||
grid.addColumn<String> { h -> h[entry.key] }.caption = entry.key
|
||||
}
|
||||
grid.styleName = "smallgrid"
|
||||
|
||||
layoutOutput2.addComponent(lblOutput2)
|
||||
layoutOutput2.addComponent(grid)
|
||||
|
||||
return layoutOutput2
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create output Spreadsheet section.
|
||||
*/
|
||||
private fun createSpreadsheetSection(): VerticalLayout {
|
||||
val layoutOutput2 = VerticalLayout()
|
||||
|
||||
val lblOutput2 = Label("OUTPUT (spreadsheet):")
|
||||
lblOutput2.setHeight("24px")
|
||||
lblOutput2.styleName = ValoTheme.LABEL_COLORED
|
||||
|
||||
val dataColumnsSize = Companion.seriesValues[2].data.size
|
||||
val dataRowsSize = Companion.seriesValues.size
|
||||
|
||||
val sheet = Spreadsheet()
|
||||
if (DATA_IN_ROWS == true) {
|
||||
sheet.createNewSheet("Output", dataRowsSize + 1, dataColumnsSize + 1)
|
||||
} else {
|
||||
sheet.createNewSheet("Output", dataColumnsSize + 1, dataRowsSize + 1)
|
||||
}
|
||||
|
||||
sheet.isReportStyle = true
|
||||
sheet.setWidth(LAYOUT_WIDTH_COLUMN3)
|
||||
|
||||
val newCellStyle = sheet.workbook.createCellStyle()
|
||||
val format = sheet.workbook.createDataFormat()
|
||||
|
||||
newCellStyle.dataFormat = format.getFormat("#,##0.00")
|
||||
|
||||
val rowNames = mutableListOf("Time" + " [${simulation.model.timeUnit}]")
|
||||
for (index in 0 until dataRowsSize) {
|
||||
rowNames.add(seriesValues[index].name + " [${seriesUnits[index]}]")
|
||||
}
|
||||
|
||||
for (rowName in rowNames) {
|
||||
if (DATA_IN_ROWS) {
|
||||
sheet.createCell(rowNames.indexOf(rowName), 0, rowName)
|
||||
sheet.autofitColumn(rowNames.indexOf(rowName))
|
||||
} else {
|
||||
sheet.createCell(0, rowNames.indexOf(rowName), rowName)
|
||||
sheet.autofitColumn(rowNames.indexOf(rowName))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
run {
|
||||
var i = 0
|
||||
while (i < dataLength) {
|
||||
val point = Companion.seriesValue[2].data[i]
|
||||
Companion.seriesValue[2].updatePoint(i, point as Double * 12.0 * 100.0)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
val rows = ArrayList<LinkedHashMap<String, String>>(rowNames.size * Companion.seriesValues[0].data.size)
|
||||
var series1: Array<Number>
|
||||
var series2: ArrayList<Double>
|
||||
for (i in 0..time.indices.last) {
|
||||
val fakeBean = LinkedHashMap<String, String>()
|
||||
for (j in 0..rowNames.indices.last) {
|
||||
if (j > 0) {
|
||||
series1 = (Companion.seriesValues[j - 1].data)
|
||||
fakeBean[rowNames[j]] = series1[i].toString()
|
||||
} else {
|
||||
series2 = time
|
||||
fakeBean[rowNames[j]] = series2[i].toString()
|
||||
}
|
||||
|
||||
}
|
||||
rows.add(fakeBean)
|
||||
}
|
||||
|
||||
val cellsToRefresh = ArrayList<Cell>()
|
||||
var cell: Cell
|
||||
for (rowName in rowNames) {
|
||||
for (row in rows) {
|
||||
if (DATA_IN_ROWS) {
|
||||
cell = sheet.createCell(rowNames.indexOf(rowName), rows.indexOf(row) + 1, row[rowName]?.toDouble())
|
||||
} else {
|
||||
cell = sheet.createCell(rows.indexOf(row) + 1, rowNames.indexOf(rowName), row[rowName]?.toDouble())
|
||||
}
|
||||
|
||||
cell.cellStyle = newCellStyle
|
||||
cellsToRefresh.add(cell)
|
||||
}
|
||||
sheet.autofitColumn(rowNames.indexOf(rowName))
|
||||
}
|
||||
sheet.refreshCells(cellsToRefresh)
|
||||
|
||||
layoutOutput2.addComponent(lblOutput2)
|
||||
sheet.setSizeFull()
|
||||
layoutOutput2.addComponent(sheet)
|
||||
return layoutOutput2
|
||||
}
|
||||
|
||||
}
|
||||
15
ksdtoolkit-webapp/src/main/resources/AppWidgetset.gwt.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC "-//Vaadin//DTD Vaadin 7//EN" "https://raw.github.com/vaadin/gwt/master/distro-source/core/src/gwt-module.dtd">
|
||||
<!-- WS Compiler: manually edited -->
|
||||
<module>
|
||||
<inherits name="com.vaadin.DefaultWidgetSet" />
|
||||
<inherits name="com.vaadin.addon.charts.Widgetset" />
|
||||
<inherits name="com.vaadin.addon.spreadsheet.Widgetset" />
|
||||
<inherits name="org.dussan.vaadin.dcharts.DchartsWidgetset" />
|
||||
<set-configuration-property name="devModeRedirectEnabled" value="true" />
|
||||
<set-property name="user.agent" value="gecko1_8,safari,ie10" />
|
||||
<source path="client" />
|
||||
<source path="shared" />
|
||||
<collapse-all-properties />
|
||||
<set-property name="compiler.useSymbolMaps" value="true" />
|
||||
</module>
|
||||
BIN
ksdtoolkit-webapp/src/main/resources/ModelGenericSD.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 21 KiB |
BIN
ksdtoolkit-webapp/src/main/resources/NoModelImage.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
1
ksdtoolkit-webapp/src/main/resources/README
Normal file
@@ -0,0 +1 @@
|
||||
Please add your static resources here
|
||||
@@ -0,0 +1,45 @@
|
||||
@import "spreadsheet-valo";
|
||||
@import "spreadsheet-legacy";
|
||||
|
||||
$v-color-keywords: non-valo-theme !default;
|
||||
|
||||
@mixin spreadsheet {
|
||||
|
||||
// Very nice workaround for missing variable-exists function
|
||||
@if $v-color-keywords == non-valo-theme {
|
||||
@include spreadsheet-legacy;
|
||||
} @else {
|
||||
@include spreadsheet-valo;
|
||||
}
|
||||
|
||||
span.code-snippet {
|
||||
font-family: "Courier New",Courier,monospace;
|
||||
}
|
||||
|
||||
.v-label-overlay-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sheet-image > .v-csslayout {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
|
||||
.v-button-minimize-button {
|
||||
top: -14px;
|
||||
position: absolute;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
|
||||
&::after {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-shadow: none;
|
||||
-ms-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.v-button-caption {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
@mixin grouping {
|
||||
.col-group-pane,
|
||||
.col-group-freeze-pane {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
z-index: 11;
|
||||
background-color: $address-field-background;
|
||||
|
||||
border-bottom: $header-border;
|
||||
box-sizing: border-box;
|
||||
|
||||
.grouping {
|
||||
position: absolute;
|
||||
height: 7px;
|
||||
border-top: 2px solid $spreadsheet-header-grouping-color;
|
||||
border-left: 2px solid $spreadsheet-header-grouping-color;
|
||||
cursor: pointer;
|
||||
|
||||
.expand {
|
||||
position: relative;
|
||||
top: -7px;
|
||||
float: right;
|
||||
right: -4px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background-color: $spreadsheet-header-grouping-color;
|
||||
color: white;
|
||||
border: none;
|
||||
line-height: 11px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.plus {
|
||||
border: none;
|
||||
|
||||
.expand {
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grouping.inversed {
|
||||
border-right: 2px solid $spreadsheet-header-grouping-color;
|
||||
border-left: none;
|
||||
.expand {
|
||||
right: initial;
|
||||
left: -4px;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.col-group-freeze-pane {
|
||||
overflow: hidden;
|
||||
border-right: $spreadsheet-freeze-pane-border;
|
||||
}
|
||||
|
||||
.col-group-border {
|
||||
position: absolute;
|
||||
|
||||
.border {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 15;
|
||||
border-bottom: 1px dotted $spreadsheet-header-grouping-color;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.row-group-pane,
|
||||
.row-group-freeze-pane {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
|
||||
z-index: 11;
|
||||
background-color: $address-field-background;
|
||||
|
||||
border-right: $header-border;
|
||||
box-sizing: border-box;
|
||||
|
||||
.grouping {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
border-top: 2px solid $spreadsheet-header-grouping-color;
|
||||
border-left: 2px solid $spreadsheet-header-grouping-color;
|
||||
cursor: pointer;
|
||||
|
||||
&.plus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.expand {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: 2px;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background-color: $spreadsheet-header-grouping-color;
|
||||
color: white;
|
||||
border: none;
|
||||
line-height: 11px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.plus {
|
||||
.expand {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.grouping.inversed {
|
||||
border-top: none;
|
||||
border-bottom: 2px solid $spreadsheet-header-grouping-color;
|
||||
.expand {
|
||||
bottom: initial;
|
||||
top: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.row-group-freeze-pane {
|
||||
overflow: hidden;
|
||||
border-bottom: $spreadsheet-freeze-pane-border;
|
||||
}
|
||||
|
||||
.row-group-border {
|
||||
position: absolute;
|
||||
|
||||
.border {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
z-index: 15;
|
||||
border-right: 1px dotted $spreadsheet-header-grouping-color;
|
||||
/*margin-top: 20px;*/
|
||||
}
|
||||
}
|
||||
|
||||
.expandbutton {
|
||||
height: 18px;
|
||||
width: 11px;
|
||||
line-height: 18px;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: darken($spreadsheet-header-grouping-color, 50%);
|
||||
|
||||
span {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
|
||||
.expandbutton:active {
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
.col-group-summary {
|
||||
.expandbutton {
|
||||
margin-left: auto;
|
||||
margin-right: 4px;
|
||||
/*margin-top: 7px;*/
|
||||
}
|
||||
}
|
||||
.row-group-summary {
|
||||
.expandbutton {
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.grouping-corner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 15;
|
||||
border-right: $header-border;
|
||||
border-bottom: $header-border;
|
||||
box-sizing: border-box;
|
||||
background-color: $address-field-background;
|
||||
}
|
||||
.col-group-summary {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
border-bottom: $header-border;
|
||||
border-right: $header-border;
|
||||
background-color: $address-field-background;
|
||||
z-index: 15;
|
||||
}
|
||||
.row-group-summary {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
border-bottom: $header-border;
|
||||
border-right: $header-border;
|
||||
background-color: $address-field-background;
|
||||
left: 0;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.v-ie & .grouping .expand {
|
||||
line-height: 13px;
|
||||
}
|
||||
}
|
||||