-
Notifications
You must be signed in to change notification settings - Fork 0
GuideForDevelopers
Useful links:
A few extra spaces can make a huge difference to how human-readable code is. For example, this
if(i<3)x=x+Math.PI*2;
is a lot less readable than this
if ( i < 3 )
x += Math.PI * 2;
despite being the same from the compiler's point of view.
Copyright information should go at the very top of the file
Import statements should be split into two groups with an empty line between, and ordered alphabetically within these groups. The first group is of external imports, i.e. those not written as part of iDynoMiCS 2; the second group is, of course, those written as part of iDynoMiCS 2. For example:
import java.math.BigInteger;
import java.util.LinkedList;
import agent.Agent;
import agent.Species;
import compartment.Compartment;
Please keep line lengths to a maximum of 80 characters wherever possible. This makes it easier to reading code on small screens, and to compare code side-by-side on one wider screen. To make this easier, you can set up Eclipse to show a margin guide:
TODO wrapping lines
Constants should always be in capitals, with words separated by underscores (known as SCREAMING_SNAKE_CASE). For example:
public final static int ONE = 1;
public final static double ONE_POINT_ZERO = 1.0;
Classes and interfaces should be in lower case, except the first letter of each word which should be a capital (known as CamelCase). For example:
public interface AnInterface
{
...
}
private Class()
{
...
}
Packages and public variables should use the slightly different CamelBack convention: the first letter is lowercase. For example:
package packageName;
public int variable;
public double nextVariable;
Protected and private variables should be the same as public, except that we place an underscore at the start. However, this can be cumbersome if the variable name is a single letter, and so can be omitted. For example:
protected double _protectedVariable;
private int _privateVariable;
private double a;
As a general rule of thumb: the more widely a name is used, the more descriptive (and therefore longer) it should be. So the single letter name is OK for a private variable, since all references to it will be in the file where it is defined. If you would like to abbreviate a long variable name, please explain the abbreviation in the javadoc at initialization. For example:
public double aLongVariableNameWithASpecificPurpose;
could become
/**
* A variable with the specific purpose of demonstrating how to explain yourself
* to other programmers with different backgrounds to your own.
*/
public double specPurp;
Commenting is your friend, whether you're writing code, reading someone else's, or even reading your own several weeks/months/years later. That said, the best code needs little in the way of comments as it is clear through good naming and layout.
Block comments span several lines, and are useful for long comments. Classes, variables, expressions, etc., should be described using Javadoc comments starting with a /**
(shown in blue in Eclipse), whereas all others start with `/*' (green).
/**
* \brief Short description about what this method does.
*
* A bit more information about what this method does.
*
* @param number An integer number required as a parameter.
* @returns A double number calculated from the input.
*/
public double aMethod(int number)
{
/*
* Long internal comments should go here. These are useful for those who want
* to understand how a method works, rather than what it does.
*/
return 1.0 * number;
}
End-of-line comments are best kept for temporary comments that you intend to be deleted at some stage. For instance:
// FIXME Harry [11July1979]: This is broken!
int two = 1 + 1;
// TODO Sally [12July1979]: Is this better?
// int two = 1 + 0;
This approach may become redundant as we use GitHub's commenting more and more.
The Java code conventions (Oracle) gives advice on trailing comments (p. 8), but we strongly discourage their use. They often lead to developers breaking the 80 character line-length rule (see above).
Whenever you want to check if two strings have the same value, use
str.equals(otherStr);
rather than
str == otherStr
as the latter will instead check if the two strings are the same object (i.e. if they occupy the same position in memory).
This is unnecessarily messy
if ( booleanVariableOrExpression )
{
return true;
}
else
{
return false;
}
when this returns exactly the same result
return booleanVariableOrExpression;
This is a confusing aspect of Java, but worth learning the distinction. All data types with a lower-case name are primitives, and in iDynoMiCS we tend only to use int
, double
and boolean
the vast majority of the time. On the other hand, data types in CamelCase are objects and many familiar ones act as "wrappers" for primitives: Integer
, Double
, Boolean
, and String
(for char
).
We often need to use modulo, for example when dealing with periodic boundaries or circular geometries. The in-built Java modulus is specified using the %
sign:
// dividend % divisor = remainder
1 % 3 ; // = 1
5 % 3 ; // = 2
-2 % 3 ; // = -2
Note that the sign of the answer is always that of the dividend. If you want it to be the same sign as the divisor (as is often the case), use Math.floorMod(int dividend, int divisor)
:
// dividend % divisor = remainder
Math.floorMod(1, 3) ; // = 1
Math.floorMod(5, 3) ; // = 2
Math.floorMod(-2, 3) ; // = 1
The Math.floorMod(x,y)
method only works for int
s, so for double
s use ExtraMath.floorMod(x,y)
in the utility
package.
The random number generator (RNG) in iDynoMiCS 2 is initialized with a random seed, meaning that a simulation can be re-run in exactly the same way so long as the same random seed is used. However, this is only possible because we use a defined random number generator, namely the MTRandom
that is initialized in ExtraMath
. If a different RNG is used by mistake, it may no longer be possible to reproduce simulations in the way we would like. Furthermore, uses of a different RNG are very hard to locate in the code.
Please, please, please do not use methods like
Math.random()
or
Random rand = new Random();
rand.nextInt(bound);
Aspects are introduced to have a flexible way of handling and storing properties and behavior of the java object that hosts them, as well as to ensure sub-models always work with up-to-date information. An aspect can be any java object, yet when creating an aspect (stored in an Aspect object) a distinction is made between three different types: PRIMARY, CALCULATED and EVENT.
Primary aspects store information or properties and are preferable the only place this information is stored (such as a double that represents an Agent's mass or a String that tells an output writer what solute should be displayed). The following example shows the creation of a simple primary state in xml:
<aspect name="pigment" type="String" value="RED" />
Here 'name' is the name other aspects use to refer to this aspect. 'type' indicates what type of aspect should be created and stored, value is the String representation of the content of the stored object. This is generally the scenario to store simple objects like primitive types or simple java objects that are easily represented in a string. including simple arrays (see example bellow).
<aspect name="exampleState" type="double[]" value="2.0, 5.1" />
When storing arrays (indicated with the appended []) the individual array values are always comma-separated.
More complex objects may also be stored as a primary aspect, when such an object is stored no value is indicated but instead the child nodes of the parent node are passed to the java object. These child nodes are object specific and are handled by the object's XMLable implementation (see paragraph XMLable interface).
<aspect name="body" type="body">
<point position="12.2, 12.2" />
</aspect>
Calculated aspects are used to calculate a property that depends on other aspects. For example calculated aspect may define an agents volume based on it's mass and density. Calculated aspects do not store any information except for on what primary aspects they depend on. There are two way's of defining a calculated aspect: 1) calling a pre-made calculated aspect:
<aspect name="surfaces" type="calculated" class="AgentSurfaces" input="body" package="agent.state.library." />
Here 'name' is the name other aspects use to refer to this aspect. 'type' indicates what type of aspect should be created. 'class' indicates what the java class name of the calculated object is that should be created. 'input' set the required input aspects and 'package' indicate the java package in which this java class is stored. When the java class has been added to the general/classLibrary.xml no package specification is required in the protocol file.
- defining the calculated state as a mathematical expression:
<aspect name="volume" type="calculated" class="StateExpression" input="mass/density" />
This type of calculated aspect always has "StateExpression" as it's defined class, yet in stead of defining it's input as a comma separated list of aspects it's input is directly defined as a mathematical expression (see paragraph Using expressions). Whenever this calculated state is called it will always evaluate this expression with the up-to-date value's and thus return an up to date double value.
NOTE: the input aspects of an StateExpression should always be or castable as java double value.
The third type of aspects are event aspects. This type of aspect does not store or return any information, but instead when it is called it can mutate primary agent states. For example create a new sibling and adjusting the cell sizes when a coccoid cell divides:
<aspect name="divide" type="event" class="CoccoidDivision" input="mass,radius,body"
package="agent.event.library." />
As can be seen in the above expression aspects of the event type can be defined in a similar way as calculated states where the only difference is that the 'name' attribute now reads event rather than 'calculated'.
Note that, if the object that implements the aspect interface is Copyable all of it's aspects should be Copyable to (the Copier.copy(Object) should be able to return a deep copy of the aspect).
Only one thing is required to use aspect with your java object. Your object needs to implement the AspectInteface. This interface requires you to add one field and one method to your class
/* The aspect registry can be used to store any java object: <Object>, but can
* also be use to store more specific objects types: <Double> */
public AspectReg<Object> aspectRegistry = new AspectReg<Object>();
/* Allows for direct access to the aspect registry */
public AspectReg<?> reg() {
return aspectRegistry;
}
The aspect interface itself implements all methods to further work with aspects, the following three are essential when working with aspects:
void loadAspects(Node xmlNode)
This method loads all aspects from the given parent node, this method should be called where you implement the XMLable interface.
boolean isAspect(String aspect)
This method returns true if the aspect exists in the root registry or in any of it's branches (see branched aspect registries). Since every agent/aspect owner may be unique check whether the called aspect is defined in this aspect registry or in one of it's branches
Object getValue(String aspect)
This method returns any aspect as java object if the aspect exists in the root registry or in any of it's branches (see branched aspect registries). If the aspect cannot be identified it will return null.
Apart from the getValue method there is a set of methods that directly return's (or attempts to) in the specified form (thus it does no longer require additional casting). These method's work for any primary or calculated aspect that can be returned in the requested form.
Double getDouble(String aspect)
Double[] getDoubleA(String aspect)
String getString(String aspect)
String[] getStringA(String aspect)
Integer getInt(String aspect)
Integer[] getIntA(String aspect)
Float getFloat(String aspect)
Float[] getFloatA(String aspect)
Boolean getBoolean(String aspect)
Boolean[] getBooleanA(String aspect)
The aspect registry is what hold's the aspect's linked to a key (String), an aspect registry can also have modules or branches. These are additional aspect registries which are included in the root aspect registry. This approach is used to allow direct access of species aspect's trough an agent. Any aspect registry can have any number of branches with any number of branches out of those branches, though agents will always have only a single branch/module which is the species aspect registry, this species aspect registry may than have any additional species modules which are handy to store specific behavior and parameters in a single place.
Typical species modules would be: the behavior of a coccoid cell, the metabolism of this subgroup, or specific features/changes of a mutant strain.
There is one GOLDEN RULE concerning branched aspect registry, when duplicate named aspects exist within the aspect tree the aspect closest to the root will override the further branch, but if two separate, independent branches use the same aspect name the first one encountered will be used, hence:
You can overrule aspects of submodules, but individual submodules cannot override each other!
This approach can be used for example if an agent wants to vary it's individual parameter from what is defined in the species aspect registry. Or, when you want to implement a species submodule which fit's you needs except for a single parameter or behavior.
Typically you should not interact with the aspect registry directly unless: you are all are creating a new AspectInterface implementing object which has mutable aspects, in that scenario you would implements:
/* setting an aspect in the root aspect registry */
public void set(String key, Object aspect)
{
aspectRegistry.set(key, aspect);
}
/* performing an event (which may result in mutating aspects in the root registry */
public void event(aspectObject compliant, double timestep, String event)
{
aspectRegistry.doEvent(this, compliant, timestep, event);
}
Some aspect interface implementing objects deliberately have no set method since they are not intended to be mutated (for example species).
In order to create a custom calculated state (apart from a direct StateExpression calculated state) you need to create a new method extending the Calculated class. Any calculated state only needs one method:
public Object get(AspectInterface aspectOwner)
{
return someOperation(input[0], input[1])
}
hence a calculated state has excess to all of the agent's other aspects, but only call's the ones that are specifically assigned in the protocol file (available as String[] input). NOTE: this may be changed to be a hashMap instead in the future.
Like calculated aspects also Event aspects only require the extending the Event class and implementing a single method:
public void start(AspectInterface initiator, AspectInterface compliant, Double timeStep)
{
/* do some event here /*
}
Note that it may be useful to cast the initiator and compliant to their native object class if you want to call methods that are specific for those objects, (or the set method). For example:
Agent mother = (Agent) initiator;
Agent daughter = (Agent) compliant;
mother.set(input[0], initiator.getDouble(input[0])/2.0);
/* etc. */
- Agent
- Species
- ProcessManager
Some definitions:
- Concentration is the amount of substance dissolved per volume. There are two ways of quantifying this substance - mass or moles - and the relative molecular mass (RMM) of a substance is needed to convert between the two. Examples of units of concentration are kg m-3 and mol L-1 (also known as molar, M).
-
Flow is the rate of transfer from one spatial region to another, and the term is often used to describe the transfer of dissolved solutes (mass flow) and of the bulk liquid (volume flow).
- Mass flow is given in units of substance per unit time (e.g. kg h-1 or mol s-1).
- Volume flow is given in units of volume per unit time (e.g. m3 h-1 or L s-1).
- Dilution is the rate at which volume of bulk liquid enters/leaves a volume, and is essentially the volume flow normalized by the volume: D = F/V. Dilution rate has units of per time (e.g. h-1 or s-1).
- Flux is the rate of transfer of substance from one spatial region to another across an area, and is given in units of substance per unit area per unit time (e.g. kg m-2 h-1).
Useful links:
Useful links:
- Atlassian Git Tutorial: Workflows
- A Visual Git Reference
- Code Tuts+
- A Quick Introduction to Version Control with Git and GitHub
- Commit and push all changes you have made in your branch.
- Close all editors/IDEs and open SmartGit.
- Make sure the branches you want to merge are up to date by:
- Check-out branch A (yours) and pull (ctrl+shift+7 to view your branches).
- Check-out branch B (Master) and pull.
- Check-out the branch that will receive the merge commit (your branch).
- Select the branch you want to merge on top of that (Master) and click merge.
- If there are conflicts, open all files with the 'conflicts' tag and resolve conflicts (the conflicts resolve tool can be used to view your file on the left, the file from master on right and the result in the middle). Per conflict you can pick either the changes from your file or from master or write something new all together, you need to look carefully which side has the correct code.
- After resolving the conflicts in a file, 'stage' the file.
- After all conflicts in all files are resolved and all the changes have been staged commit the changes.
- If there are (no more) conflicts push the merge-commit.
- now there should be no more conflicts between your branch and the master branch and you can create a pull-request in GitHub.
GitHub enables discussion of issues and aspects of specific code, but there's nothing quite like a face-to-face conversation to resolve these and more general topics. We hold a Google hangout roughly every week or two
Please set the git configuration setting core.autocrlf
to false
for contributing to iDynoMiCS 2. It's generally a good idea to do this for your global settings if you are working on projects that are developed in both Windows and in Unix (Mac/Linux) environments. This only needs to be set once.
To set this in Eclipse with eGit installed, go to Preferences > Team > Git > Configuration:
In the command line, this is done like so:
git config --global core.autocrlf false
If you've configured Git nicely and yet it still misbehaves, try these little hacks in your URL bar...
Problem | Fix |
---|---|
Ignore whitespace | ?w=1 |
Change the tab size | ?ts=4 |