-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modify slider values #63
Comments
So setting global variables, including sliders and switches is very easy with pyNetLogo. Figured it out in 5 minutes, I was looking for a separate function in pyNetLogo but you can just use the I thought let's update the tutorial quickly, but of course that came crashing down hard. So the rest of the hour was spent trying to fix that, unfortunately to moderate succes. I filed a bug here: #65. And of course no debug process is complete without an IDE bug and having to roll back PyCharm. Anyway, the updated tutorial, which crashes halfway, is available here. The main takeaway is, for anyone finding this issue or wanting to use it as reference:
|
I fixed #65 and have already made a few tutorial updates to reflect this fix and other changes. Any additional suggestions for the tutorial are still very welcome. |
Do you know if it's also possible to read-out current, minimum and maximum slider values? They are present in the model code, so it should be possible. That would be a great feature, because then you can just ask to vary though the pre-defined ranges. |
How would you query these within NetLogo? |
My approach would be to directly take them from the NetLogo model file itself, they are clearly in there:
Once you have those you can do all kind of things, filling a hypervolume, sensitive analysis, etc. |
So, as far as you know, there is no command or other way to get these numbers out? In that case, it will be hard to get them out to Python without writing a lot of additional novel java code. |
No, but I also haven't really searched for it. We can also asked it on the NetLogo repo. |
The scope of pynetlogo, as with the mathematical link, is to send NetLogo commands to NetLogo and query reporters. So, if what you want can be done within this scope it is okay. My suggestion would indeed be to check the NetLogo repo. |
I dove a bit deeper into this. The thing we need to read out are called the widgets. They are basically all interface elements, but also saved in the There are two general approaches to this.
The extending the Java partimport org.nlogo.api.*;
import org.nlogo.headless.HeadlessWorkspace;
import org.nlogo.workspace.AbstractWorkspace;
import org.nlogo.window.GUIWorkspace;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NetLogoLink {
private org.nlogo.workspace.Controllable workspace = null;
private java.io.IOException caughtEx = null;
private boolean isGUIworkspace;
private static boolean blockExit = true;
public NetLogoLink(Boolean isGUImode, Boolean is3d) {
// Existing constructor implementation
}
// Other existing methods ...
public List<Map<String, Object>> getInputProperties() {
List<Map<String, Object>> inputProperties = new ArrayList<>();
LogoList widgets;
try {
widgets = (LogoList) workspace.report("widgets");
} catch (CompilerException | LogoException e) {
e.printStackTrace();
return inputProperties;
}
for (Object widget : widgets) {
String[] lines = widget.toString().split("\n");
String widgetType = lines[0];
Map<String, Object> properties = new HashMap<>();
if ("SLIDER".equals(widgetType)) {
String name = lines[1];
double min = Double.parseDouble(lines[6]);
double max = Double.parseDouble(lines[7]);
double value = Double.parseDouble(lines[8]);
double increment = Double.parseDouble(lines[9]);
properties.put("type", "SLIDER");
properties.put("name", name);
properties.put("min", min);
properties.put("max", max);
properties.put("value", value);
properties.put("increment", increment);
} else if ("CHOOSER".equals(widgetType)) {
String name = lines[1];
String[] choices = lines[6].split(" ");
String value = lines[7];
properties.put("type", "CHOOSER");
properties.put("name", name);
properties.put("choices", choices);
properties.put("value", value);
} else if ("SWITCH".equals(widgetType)) {
String name = lines[1];
boolean value = Boolean.parseBoolean(lines[6]);
properties.put("type", "SWITCH");
properties.put("name", name);
properties.put("value", value);
} else {
continue;
}
inputProperties.add(properties);
}
return inputProperties;
}
} Python partimport pandas as pd
from jnius import autoclass
class NetLogoLink:
def __init__(self, isGUImode=True, is3d=False):
# Existing constructor implementation
# Other existing methods ...
def get_input_properties(self):
JavaList = autoclass('java.util.ArrayList')
JavaMap = autoclass('java.util.HashMap')
java_input_properties = self.workspace.getInputProperties()
input_properties_list = []
for i in range(java_input_properties.size()):
java_map = java_input_properties.get(i)
properties = {}
for key in java_map.keySet():
value = java_map.get(key)
if key == "choices":
value = [value.get(i) for i in range(value.size())]
properties[key] = value
input_properties_list.append(properties)
input_properties_df = pd.DataFrame(input_properties_list)
return input_properties_df And the pure-Python implementation more like this: Python implantationimport re
import pandas as pd
class NetLogoLink:
def __init__(self, model_file=None):
self.model_file = model_file
def load_model(self, model_file):
self.model_file = model_file
def get_input_properties_pure_python(self):
input_properties_list = []
if not self.model_file:
raise ValueError("Model file not loaded. Call load_model() first.")
with open(model_file, 'r') as file:
content = file.read()
slider_pattern = re.compile(r'SLIDER\n([^@]+)')
sliders = slider_pattern.findall(content)
for slider in sliders:
lines = slider.split('\n')
properties = {
'type': 'SLIDER',
'name': lines[0],
'min': float(lines[5]),
'max': float(lines[6]),
'value': float(lines[7]),
'increment': float(lines[8]),
}
input_properties_list.append(properties)
chooser_pattern = re.compile(r'CHOOSER\n([^@]+)')
choosers = chooser_pattern.findall(content)
for chooser in choosers:
lines = chooser.split('\n')
properties = {
'type': 'CHOOSER',
'name': lines[0],
'choices': lines[5].split(' '),
'value': lines[6],
}
input_properties_list.append(properties)
switch_pattern = re.compile(r'SWITCH\n([^@]+)')
switches = switch_pattern.findall(content)
for switch in switches:
lines = switch.split('\n')
properties = {
'type': 'SWITCH',
'name': lines[0],
'value': lines[5].lower() == 'true',
}
input_properties_list.append(properties)
input_properties_df = pd.DataFrame(input_properties_list)
return input_properties_df Another thing I would like to implement is a The goal from both is to reduce Python code and leave as much of the stuff in NetLogo itself. Given that, you test you NetLogo model with a few lines of code, and if you want to adjust values, you can just start with Which approach do you prefer? |
One other thing I thought of is that counters are not readable from the NetLogo file in runtime. On the other hand, the counter variable names (reporters) are, so we could add them as reporters automatically of course. So when choosing an approach, it probably depends on how far we want to scale this. A pure-Python scraping approach would be feasible to implement for my by myself, a hybrid Java-Python one using the widgets API I can do the Python part. |
I am inclined to take the hybrid java route. I expect this to be more robust in the long run. |
Is it possible to vary the values of sliders using pyNetLogo? If so how?
In the docs I can only find
.write_NetLogo_attriblist()
, but that seems to modify agent values.The text was updated successfully, but these errors were encountered: