Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • dc-oman1/swiftsim
  • swift/swiftsim
  • pdraper/swiftsim
  • tkchan/swiftsim
  • dc-turn5/swiftsim
5 results
Select Git revision
Show changes
Showing
with 4056 additions and 984 deletions
#!/bin/bash
. tap-functions
plan_no_plan
is "$(./test_argparse -f --path=/path/to/file a 2>&1)" 'force: 1
path: /path/to/file
argc: 1
argv[0]: a'
is "$(./test_argparse -f -f --force --no-force 2>&1)" 'force: 2'
is "$(./test_argparse -i 2>&1)" 'error: option `-i` requires a value'
is "$(./test_argparse -i 2 2>&1)" 'int_num: 2'
is "$(./test_argparse -i2 2>&1)" 'int_num: 2'
is "$(./test_argparse -ia 2>&1)" 'error: option `-i` expects an integer value'
is "$(./test_argparse -i 0xFFFFFFFFFFFFFFFFF 2>&1)" \
'error: option `-i` Numerical result out of range'
is "$(./test_argparse -s 2.4 2>&1)" 'flt_num: 2.4'
is "$(./test_argparse -s2.4 2>&1)" 'flt_num: 2.4'
is "$(./test_argparse -sa 2>&1)" 'error: option `-s` expects a numerical value'
is "$(./test_argparse -s 1e999 2>&1)" \
'error: option `-s` Numerical result out of range'
is "$(./test_argparse -f -- do -f -h 2>&1)" 'force: 1
argc: 3
argv[0]: do
argv[1]: -f
argv[2]: -h'
is "$(./test_argparse -tf 2>&1)" 'force: 1
test: 1'
is "$(./test_argparse --read --write 2>&1)" 'perms: 3'
is "$(./test_argparse -h)" 'Usage: test_argparse [options] [[--] args]
or: test_argparse [options]
A brief description of what the program does and how it works.
-h, --help show this help message and exit
Basic options
-f, --force force to do
-t, --test test only
-p, --path=<str> path to read
-i, --int=<int> selected integer
-s, --float=<flt> selected float
Bits options
--read read perm
--write write perm
--exec exec perm
Additional description of the program after the description of the arguments.'
#include "argparse.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *const usages[] = {
"test_argparse [options] [[--] args]",
"test_argparse [options]",
NULL,
};
#define PERM_READ (1 << 0)
#define PERM_WRITE (1 << 1)
#define PERM_EXEC (1 << 2)
struct stuff {
const char *path[10];
int npath;
};
static int callback(struct argparse *self, const struct argparse_option *opt) {
printf("Called back... %s\n", *(char **)opt->value);
struct stuff *data = (struct stuff *)opt->data;
data->path[data->npath] = *(char **)opt->value;
data->npath++;
return 1;
}
int main(int argc, const char **argv) {
int force = 0;
int self_gravity = 0;
int int_num = 0;
float flt_num = 0.f;
struct stuff data;
data.npath = 0;
data.path[0] = NULL;
const char *buffer;
int perms = 0;
int npath;
struct argparse_option options[] = {
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_BOOLEAN('f', "force", &force, "force to do", NULL, 0, 0),
OPT_BOOLEAN(0, "self-gravity", &self_gravity, "use self gravity", NULL, 0,
0),
OPT_STRING('P', "path", &buffer, "path to read", &callback,
(intptr_t)&data, 0),
OPT_INTEGER('i', "int", &int_num, "selected integer", NULL, 0, 0),
OPT_FLOAT('s', "float", &flt_num, "selected float", NULL, 0, 0),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usages, 0);
argparse_describe(
&argparse,
"\nA brief description of what the program does and how it works.",
"\nAdditional description of the program after the description of the "
"arguments.");
argc = argparse_parse(&argparse, argc, argv);
if (force != 0) printf("force: %d\n", force);
if (self_gravity != 0) printf("self_gravity: %d\n", self_gravity);
if (data.npath > 0) {
for (int i = 0; i < data.npath; i++) printf("path: %s\n", data.path[i]);
}
if (int_num != 0) printf("int_num: %d\n", int_num);
if (flt_num != 0) printf("flt_num: %g\n", flt_num);
if (argc != 0) {
printf("argc: %d\n", argc);
int i;
for (i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, *(argv + i));
}
}
if (perms) {
printf("perms: %d\n", perms);
}
return 0;
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Update generated configuration files, i.e. do work so that a # Update generated configuration files, i.e. do work so that a
# developer checkout can be configured. # developer checkout can be configured.
autoreconf --install --symlink autoreconf --install --symlink
exit exit
This diff is collapsed.
Subproject commit 0523433a7880e58e22b2215afd0104ea58786962
This diff is collapsed.
# This file is part of SWIFT. # This file is part of SWIFT.
# Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk), # Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk),
# Matthieu Schaller (matthieu.schaller@durham.ac.uk). # Matthieu Schaller (schaller@strw.leidenuniv.nl).
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
......
build/*
make.bat
.. _NewTask:
How to add a new task to SWIFT?
=================================
.. highlight:: c
.. toctree::
:maxdepth: 0
This tutorial will step through how to add a new task to swift. First we will go through the
idealology of adding a new task to SWIFT. This will be followed by an example of how to add a task
for an imposed external gravitational field to SWIFT and a task to include "cooling" to the gas particles.
In the simplest case adding a new tasks requires changes to five files, namely:
* task.h
* cell.h
* timers.h
* task.c
* engine.c
Further, implementation details of what the task will then do should be added to another file
(for example runner_myviptask.c) which will contain the actual task implementation.
So now lets look at what needs to change in each of the files above, starting with task.h
--------------
**task.h**
--------------
Within task.h there exists a structure of the form::
/* The different task types. */
enum task_types {
task_type_none = 0,
task_type_sort,
task_type_self,
task_type_pair,
.
.
.
task_type_my_new_task,
task_type_psort,
task_type_split_cell,
task_type_count
};
Within this task structure your new task should be added. Add the task entry anywhere in the struct before the
task_type_count member. This last entry is used to count the number of tasks and must always be the last entry.
--------------
**task.c**
--------------
Within task.c the addition of the new task type must be include in a character list (which at the moment is only
used for debugging purposes)::
/* Task type names. */
const char *taskID_names[task_type_count] = {
"none", "sort", "self", "pair", "sub",
"ghost", "kick1", "kick2", "send", "recv",
"link", "grav_pp", "grav_mm", "grav_up", "grav_down",
"my_new_task", "psort", "split_cell"};
The new task type should be added to this list in the same order as it was added within the task_types struct in
task.h
--------------
**cell.h**
--------------
cell.h contains pointers to all of the tasks associated with that cell. You must include your new task type
here e.g::
struct task *my_new_task;
--------------
**timers.h**
--------------
Within timers.h is an enumerated list of timers associated with each task. The timers measure the time required
to execute a given task and this information is used in improve scheduling the task in future iterations::
/* The timers themselves. */
enum {
timer_none = 0,
timer_prepare,
timer_kick1,
.
.
.
timer_new_task,
timer_step,
timer_count,
};
--------------
**engine.c**
--------------
Finally, in engine.c the new task is added so that the scheduler knows to include the task in the list of tasks
to be scheduled. Knowing where to add the task in engine.c is a little bit more difficult. This will depend on
the type of task involved and whether it is a task that acts only on a individual particle independent of other
particles (e.g. a cooling a task) or whether the task depends on other tasks (e.g. density, force or feedback).
If we assume that the task is a particle only task then the first place to modify is the engine_mkghosts()
function. Within this function the new task must be added to the list of tasks
(within the c->nodeID == e->nodeID if clause)::
/* Generate the external gravity task*/
c->my_new_task = scheduler_addtask(s, task_type_my_new_task, task_subtype_none, 0, 0,
c, NULL, 0);
That's pretty much it - but what about dependencies and conflicts?
Remember SWIFT automatically handles conflicts (by understanding which tasks need to write to the same data) so
you (the developer) don't need to worry about conflicts. Dependencies do however need to be managed and they will
be task specific. The following two examples, implementing cooling and an imposed external gravitational field
will illustrate how dependencies should be treated.
Examples:
:ref:`ExternalGravityExample`
:ref:`CoolingExample`
.. _CoolingExample:
Cooling Example
--------------------------
An example of how to implement a particle cooling task in SWIFT
===================================================================
.. toctree::
:maxdepth: 1
.. _ExternalGravityExample:
External Gravity Task Example
----------------------------------
An example of how to implement an external gravity task in SWIFT
=====================================================================
An external gravitational field can be imposed in SWIFT to mimic self-gravity. This is done by assigning
a gravitational force that falls as $1/ r^2$ (mathjax support to be included).
In order to do this we update the files as described in :ref:`NewTask`. For the specific case of adding an
external graviational field the additions are as follows:
--------------
**task.h**
--------------
Code (snapshot Nov 2015)::
/* The different task types. */
enum task_types {
task_type_none = 0,
task_type_sort,
task_type_self,
task_type_pair,
task_type_sub,
task_type_ghost,
task_type_kick1,
task_type_kick2,
task_type_send,
task_type_recv,
task_type_link,
task_type_grav_pp,
task_type_grav_mm,
task_type_grav_up,
task_type_grav_down,
**task_type_grav_external,**
task_type_psort,
task_type_split_cell,
task_type_count
};
Task of type - task_type_grav_external - added to list of tasks.
--------------
**task.c**
--------------
Code (snapshot Nov 2015)::
/* Task type names. */
const char *taskID_names[task_type_count] = {
"none", "sort", "self", "pair", "sub",
"ghost", "kick1", "kick2", "send", "recv",
"link", "grav_pp", "grav_mm", "grav_up", "grav_down", "grav_external",
"psort", "split_cell"
};
Task added to list of task names (used only for debugging purposed).
--------------
**cell.h**
--------------
Code (snapshot Nov 2015)::
/* The ghost task to link density to interactions. */
struct task *ghost, *kick1, *kick2, *grav_external;
Struture of type "task" declared (or pointer to a task at least).
--------------
**timers.h**
--------------
Code (snapshot Nov 2015)::
/* The timers themselves. */
enum {
timer_none = 0,
timer_prepare,
timer_kick1,
timer_kick2,
timer_dosort,
timer_doself_density,
timer_doself_force,
timer_doself_grav,
timer_dopair_density,
timer_dopair_force,
timer_dopair_grav,
timer_dosub_density,
timer_dosub_force,
timer_dosub_grav,
timer_dopair_subset,
timer_doghost,
timer_dograv_external,
timer_gettask,
timer_qget,
timer_qsteal,
timer_runners,
timer_step,
timer_count,
};
The timer list is updated to include a timer task.
--------------
**engine.c**
--------------
Code (snapshot Nov 2015)::
void engine_mkghosts(struct engine *e, struct cell *c, struct cell *super) {
int k;
struct scheduler *s = &e->sched;
/* Am I the super-cell? */
if (super == NULL && c->nr_tasks > 0) {
/* Remember me. */
super = c;
/* Local tasks only... */
if (c->nodeID == e->nodeID) {
/* Generate the external gravity task*/
c->grav_external = scheduler_addtask(s, task_type_grav_external, task_subtype_none, 0, 0,
c, NULL, 0);
/* Enforce gravity calculated before kick 2 */
scheduler_addunlock(s, c->grav_external, c->kick2);
}
}
}
The first function call adds the task to the scheduler. The second function call takes care of the dependency
involved in imposing an external gravitational field. These two functions are worth considering due to their
obvious importance.
The function prototype for the addtask function is (**found in scheduler.c**)::
struct task *scheduler_addtask(struct scheduler *s, int type, int subtype,
int flags, int wait, struct cell *ci,
struct cell *cj, int tight) {
This function adds a task to the scheduler. In the call to this function in engine.c we used the actual
parameters **s** for the scheduler, **task_type_grav_external** for the (task) type, task_subtype_none for
the (task) subtype, zeros for the flags and wait parameters, **c** for the pointer to our cell, NULL for the
cell we interact with since there is none and 0 for the tight parameter.
The function prototype for the addunlock function is(**found in scheduler.c**)::
void scheduler_addunlock(struct scheduler *s, struct task *ta,
struct task *tb) {
This function signals when the unlock a certain task. In our case we use the external gravity task to unlock the
kick2 task - i.e. kick2 depends on external gravity. So when calling the addunlock function the
order is the **ta** task should be the task to unlock and **tb** should the task that does the unlocking.
--------------
**runner.c**
--------------
In runner.c the implementation of the external gravity task is taken care of. The function prototype is::
void runner_dograv_external(struct runner *r, struct cell *c) {
The function takes a pointer to a runner struct and a pointer to the cell struct. The entire function call is::
void runner_dograv_external(struct runner *r, struct cell *c) {
struct part *p, *parts = c->parts;
float rinv;
int i, ic, k, count = c->count;
float dt_step = r->e->dt_step;
TIMER_TIC
/* Recurse? */
if (c->split) {
for (k = 0; k < 8; k++)
if (c->progeny[k] != NULL) runner_dograv_external(r, c->progeny[k]);
return;
}
/* Loop over the parts in this cell. */
for (i = 0; i < count; i++) {
/* Get a direct pointer on the part. */
p = &parts[i];
/* Is this part within the time step? */
if (p->dt <= dt_step) {
rinv = 1 / sqrtf((p->x[0])*(p->x[0]) + (p->x[1])*(p->x[1]) + (p->x[2])*(p->x[2]));
for(ic=0;ic<3;ic++){
p->grav_accel[ic] = - const_G * (p->x[ic]) * rinv * rinv * rinv;
}
}
}
TIMER_TOC(timer_dograv_external);
}
The key component of this function is the calculation of **rinv** and then the imposition of the
**grav_accel** to this particle. **rinv** is calculated assuming the centre of the gravitational
potential lies at the origin. The acceleration of each particle then is calculated by multiplying
the graviational constant by the component of the position along one axis divided by R^3. The
gravitational acceleration is then added to the total particle acceleration **a**.
.. toctree::
:maxdepth: 1
.. _DeveloperGuide:
A Developer Guide for SWIFT
=================================
.. toctree::
:maxdepth: 1
AddingTasks/addingtasks.rst
Examples/ExternalGravity/externalgravity.rst
Examples/Cooling/cooling.rst
.. _GettingStarted:
Frequently Asked Questions
==========================
1)
2)
3)
4)
5)
.. toctree::
:maxdepth: 1
.. _GettingStarted:
Asynchronous Communication
=================================
.. toctree::
:maxdepth: 1
.. _GettingStarted:
Caching
=================================
.. toctree::
:maxdepth: 1
doc/RTD/Innovation/HeirarchicalCellDecomposition/SplitCell.png

100 KiB

doc/RTD/Innovation/HeirarchicalCellDecomposition/SplitPair.png

57.9 KiB

.. _GettingStarted:
Heirarchical Cell Decomposition
=================================
Most SPH codes rely on spatial trees to decompose the simulation space. This decomposition makes neighbour-finding simple, at the cost of computational efficiency. Neighbour-finding using the tree-based approach has an average computational cost of ~O(logN) and has a worst case behaviour of ~O(N\ :sup:`2/3`\), both cases grow with the total number of particles N. SWIFT's neighbour-finding algorithm however, has a constant scaling of ~O(1) per particle. This results from the way SWIFT decomposes its domain.
The space is divided up into a grid of rectangular cells with an edge length that is greater than or equal to the maximum smoothing of any particle in the simulation, h\ :sub:`max`\ (See :ref:`cell_decomp`).
.. _cell_decomp:
.. figure:: InitialDecomp.png
:scale: 40 %
:align: center
:figclass: align-center
Figure 1: 2D Cell Decomposition
In this initial decomposition if a particle p\ :sub:`j`\ is within range of particle p\ :sub:`i`\, both will either be in the same cell (self-interaction) or in neighbouring cells (pair-interaction). Each cell then only has to compute its self-interactions and pair-interactions for each of its particles.
The best case scenario is when each cell only contains particles that have a smoothing length equal to the cell edge length and even then, for any given particle p\ :sub:`i`\ it will only interact with 16% of the total number of particles in the same cell and surrounding neighbours. This percentage decreases if the cell contains particles whose smoothing length is less than the cell edge length. Therefore the cell decomposition needs to be refined recursively by bisecting a cell along each dimension if the following conditions are met:
1) The cell contains more than a minimum number of particles
2) The smoothing length of a reasonable number of particles within a cell is less than half the cell's edge length
.. _split_cell:
.. figure:: SplitCell.png
:scale: 40 %
:align: center
:figclass: align-center
Figure 2: Refined Cell Decomposition
Once a cell has been split its self-interactions can be decomposed into self-interactions of its sub-cells and corresponding pair interactions (See :ref:`split_cell`). If a pair of split cells share a boundary with each other and all particles in both cells have a smoothing length less than the cell edge length, then their pair-interactions can also be split up into pair-interactions of the sub-cells spanning the boundary (See :ref:`split_pair`).
.. _split_pair:
.. figure:: SplitPair.png
:scale: 40 %
:align: center
:figclass: align-center
Figure 3: Split Cell Pair Interactions
When the cells' particle interactions are split up between self-interactions and pair-interactions, any two particles who are within range of each other will either share a cell for which a cell self-interaction is defined or they will be located in neighbouring cells which share a cell pair-interaction. Therefore to determine whether particles are within range of each other it is sufficient to traverse the list of self-interactions and pair-interactions and compute the interactions therein.
.. toctree::
:maxdepth: 1
.. _GettingStarted:
Hybrid Shared/Distributed-Memory Parallelism
============================================
.. toctree::
:maxdepth: 1