Initial commit

This commit is contained in:
Siniša Sovilj
2021-01-08 16:08:25 +01:00
commit cbb13d94a0
110 changed files with 9165 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Project exclude paths
/.gradle/
/.idea/
**/build/
!src/**/build/

201
LICENSE Normal file
View 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
View 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 Googles 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).
![](images/Figure01.png?raw=true)
#### 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.
![](images/Figure02.png?raw=true)
#### 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/```
![](images/Figure03.png?raw=true)
(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.
![](images/Figure04.png?raw=true)
## 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
View 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
View 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

Binary file not shown.

View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
images/Figure02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

BIN
images/Figure03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
images/Figure04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

View 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()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View 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
1 Module.inflow outflow Module.CONSTANT Module.converter Module.outflow Module.INITIAL_STOCK INITIAL_STOCK converter CONSTANT inflow Stock Module.Stock
2 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
3 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
4 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
5 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
6 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
7 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
8 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
9 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
10 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
11 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
12 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
13 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
14 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
15 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
16 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
17 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
18 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
19 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
20 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
21 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
22 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
23 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
24 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
25 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
26 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
27 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
28 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
29 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
30 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
31 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
32 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
33 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
34 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
35 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
36 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
37 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
38 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
39 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
40 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
41 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
42 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
43 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
44 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
45 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
46 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
47 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
48 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
49 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
50 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
51 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
52 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
53 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
54 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
55 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
56 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
57 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
58 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
59 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
60 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
61 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
62 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
63 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
64 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
65 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
66 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
67 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
68 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
69 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
70 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
71 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
72 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
73 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
74 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
75 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
76 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
77 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
78 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
79 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
80 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
81 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
82 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
83 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
84 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
85 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
86 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
87 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
88 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
89 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
90 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
91 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
92 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
93 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
94 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
95 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
96 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
97 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
98 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
99 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
100 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
101 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
102 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
103 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
104 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
105 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
106 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
107 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
108 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
109 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
110 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
111 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
112 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
113 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
114 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
115 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
116 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
117 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
118 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
119 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
120 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
121 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
122 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
123 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
124 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
125 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
126 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
127 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
128 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
129 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
130 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
131 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
132 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
133 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
134 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
135 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
136 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
137 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
138 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
139 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
140 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
141 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
142 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
143 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
144 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
145 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
146 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
147 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
148 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
149 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
150 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
151 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
152 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
153 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
154 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
155 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
156 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
157 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
158 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
159 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
160 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
161 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
162 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
163 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
164 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
165 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
166 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
167 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
168 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
169 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
170 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
171 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
172 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
173 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
174 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
175 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
176 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
177 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
178 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
179 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
180 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
181 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
182 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
183 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
184 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
185 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
186 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
187 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
188 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
189 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
190 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
191 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
192 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
193 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
194 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
195 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
196 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
197 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
198 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
199 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
200 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
201 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
202 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
203 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
204 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
205 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
206 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
207 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
208 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
209 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
210 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
211 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
212 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
213 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
214 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
215 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
216 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
217 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
218 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
219 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
220 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
221 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
222 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
223 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
224 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
225 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
226 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
227 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
228 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
229 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
230 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
231 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
232 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
233 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
234 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
235 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
236 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
237 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
238 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
239 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
240 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
241 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
242 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
243 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
244 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
245 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
246 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
247 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
248 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
249 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
250 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
251 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
252 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
253 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
254 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
255 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
256 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
257 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
258 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
259 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
260 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
261 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
262 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
263 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
264 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
265 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
266 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
267 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
268 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
269 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
270 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
271 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
272 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
273 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
274 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
275 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
276 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
277 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
278 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
279 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
280 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
281 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
282 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
283 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
284 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
285 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
286 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
287 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
288 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
289 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
290 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
291 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
292 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
293 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
294 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
295 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
296 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
297 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
298 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
299 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
300 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
301 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
302 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
303 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
304 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
305 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
306 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
307 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
308 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
309 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
310 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
311 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
312 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
313 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
314 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
315 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
316 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
317 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
318 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
319 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
320 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
321 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
322 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
323 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
324 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
325 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
326 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
327 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
328 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
329 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
330 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
331 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
332 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
333 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
334 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
335 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
336 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
337 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
338 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
339 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
340 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
341 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
342 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
343 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
344 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
345 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
346 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
347 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
348 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
349 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
350 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
351 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
352 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
353 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
354 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
355 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
356 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
357 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
358 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
359 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
360 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
361 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
362 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
363 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
364 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
365 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
366 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
367 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
368 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
369 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
370 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
371 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
372 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
373 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
374 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
375 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
376 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
377 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
378 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
379 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
380 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
381 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
382 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
383 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
384 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
385 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
386 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
387 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
388 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
389 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
390 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
391 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
392 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
393 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
394 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
395 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
396 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
397 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
398 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
399 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
400 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
401 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
402 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
403 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
404 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
405 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
406 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
407 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
408 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
409 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
410 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
411 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
412 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
413 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
414 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
415 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
416 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
417 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
418 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
419 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
420 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
421 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
422 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
423 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
424 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
425 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
426 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
427 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
428 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
429 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
430 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
431 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
432 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
433 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
434 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
435 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
436 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
437 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
438 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
439 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
440 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
441 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
442 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
443 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
444 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
445 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
446 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
447 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
448 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
449 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
450 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
451 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
452 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
453 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
454 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
455 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
456 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
457 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
458 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
459 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
460 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
461 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
462 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
463 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
464 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
465 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
466 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
467 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
468 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
469 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
470 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
471 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
472 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
473 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
474 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
475 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
476 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
477 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
478 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
479 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
480 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
481 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100
482 0.833333 0.833333 10 0.008333 0.833333 100 100 0.008333 10 0.833333 100 100

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -0,0 +1,11 @@
package hr.unipu.ksdtoolkit.entities
/**
* Function interface.
* (Not used anymore, just for legacy.)
*/
interface IFunction {
fun calculateEntityValue(): Double
}

View File

@@ -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} ]"
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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
}
}
}

View File

@@ -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()
}

View File

@@ -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++
}
}
}
}

View File

@@ -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 }
}
}

View File

@@ -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 }
}
}

View File

@@ -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 }
}
}

View File

@@ -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 }
}
}

View File

@@ -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 }
}
}

View File

@@ -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:
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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") }
}
}
}
}

View File

@@ -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)
}

View File

@@ -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()
}
}
}

View 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;
}

View 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;*/
/*}*/

View 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;
}

View File

@@ -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)))
}
}

View File

@@ -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``() { }
*/
}

View File

@@ -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.
}
}

View File

@@ -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"))
}
}

View File

@@ -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---")
}
*/
}

View 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")
}
}
}

View 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

View File

@@ -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))
)
}
}
}

View 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>

View File

@@ -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
}
}

View File

@@ -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()
}
}

View 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;
}

View 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;*/
/*}*/

View 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;
}

View File

@@ -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>

View File

@@ -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>

View 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>-->

View 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>-->

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View 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>

View 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>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="insertPoint"/>
<item type="id" name="lineChart"/>
</resources>

View File

@@ -0,0 +1,4 @@
<resources>
<string name="app_name">MobSimulator</string>
<string name="input">input</string>
</resources>

View File

@@ -0,0 +1,36 @@
<resources>
<!-- Base application theme. -->
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">-->
<!-- &lt;!&ndash; Customize your theme here. &ndash;&gt;-->
<!-- <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>

View File

@@ -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"))
}
}

View 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
}

View 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

View File

@@ -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
}
}

View File

@@ -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) {
}
}

View File

@@ -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
}
}

View File

@@ -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) {
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1 @@
Please add your static resources here

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More