Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

Neural Dot Net Pt 3 The Adaline Network

Rate me:
Please Sign up or sign in to vote.
3.71/5 (16 votes)
23 Oct 200316 min read 73.2K   379   41  
A neural network library in C#.

using System;
using System.Collections;
using SharpUtils;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.Threading;

namespace Neural_Net_Library
{
	/// <summary>
	/// VALUES STRUCTURE  represents all the values that are used for accessing arrays in the library
	/// These were originally global variables in the original c++ ( yuk )
	/// </summary>
	public struct Values
	{
		/// <summary>
		/// standard values for all networks
		/// </summary>
		public const int NodeValue = 0;
		public const int LearningRate = 1;
		public const int NodeError = 0;
		public const int Weight = 0;

		/// <summary>
		/// BackPropagation Network Values
		/// </summary>
		public const int Delta = 1;
		public const int Momentum = 2;

		/// <summary>
		/// Self Organizing Network Values
		/// </summary>
		public const int Composite = 0;
		public const int Row = 1;
		public const int Column = 2;

		/// <summary>
		/// Bidirectional Associative Memory
		/// </summary>
		public const int LastNodeValue = 1;


		/// <summary>
		/// return a random value
		/// Note: The sleep( 100 ) is added as the Random function is called repeatedly in certain cases and
		/// the output is useless on a fast computer so this slows it down enough to make the values acceptable
		/// </summary>
		/// <param name="dLowerLimit">The lowest acceptable value</param>
		/// <param name="UpperLimit">The highest acceptable value</param>
		/// <returns></returns>
		public static double Random( double dLowerLimit, double dUpperLimit )
		{
			Thread.Sleep( 100 );
			double dNumber = 0.0;
			Random temp = new Random();
			dNumber = temp.Next( ( int )dLowerLimit, ( int )dUpperLimit );
			if( dNumber>0 )
			{
				dNumber -= temp.NextDouble();
				if( dNumber < -1 )
					dNumber = -1;
			}
			else
			{
				dNumber = temp.NextDouble();
				if( dNumber > 1 )
					dNumber = 1;
			}

			return dNumber;
		}

	}


	/// <summary>
	/// abstract basic class
	/// </summary>
	public abstract class Basic
	{
		private static int nID = -1;


		public Basic()
		{
			nID++;
		}

		public int ID
		{
			get
			{
				return nID;
			}
			set
			{
				nID = value;
			}
		}
	}

	/// <summary>
	/// Basic Link class that provides the links between all nodes in any network.
	/// Note the links carry the weight values that are used during the learning calculations
	/// </summary>
	public abstract class BasicLink : Basic
	{
		protected ArrayList arrayLinkValues; /// values for the link ( weight value is arraylinkvalues 0 )
		protected BasicNode bnInputNode; /// node instance link is coming from
		protected BasicNode bnOutputNode; /// node instance link is going to 

		private DebugLevel debugLevel;
		private Logger log;

		/// <summary>
		/// variables used for loading the links
		/// </summary>
		private int nInputNodeID;
		private int nOutputNodeID; 

		private int nIdentifier;

		/// <summary>
		/// link id
		/// </summary>
		public int Identifier
		{
			get
			{
				return nIdentifier;
			}
			set
			{
				nIdentifier = value;
			}
		}

		/// <summary>
		/// Used only setting up the links on load
		/// </summary>
		public int InputNodeID
		{
			get
			{
				return nInputNodeID;
			}
			set
			{
				nInputNodeID = value;
			}
		}

		/// <summary>
		///  Used only for setting up the links on load
		/// </summary>
		public int OutputNodeID
		{
			get
			{
				return nOutputNodeID;
			}
			set
			{
				nOutputNodeID = value;
			}
		}

		/// <summary>
		/// most basic constructor taking only the log file
		/// </summary>
		/// <param name="log"></param>
		public BasicLink( Logger log )
		{


			arrayLinkValues = new ArrayList();
			arrayLinkValues.Add( 0.0 );
			Identifier = ID;

			debugLevel = new DebugLevel( DebugLevel.currentLevel );

			this.log = log;
		}

		/// <summary>
		/// Constructor taking the log file and the number of links to create
		/// </summary>
		/// <param name="log"></param>
		/// <param name="nCount"></param>
		public BasicLink( Logger log, int nCount )
		{
			arrayLinkValues = new ArrayList( nCount );
			for( int i=0; i<nCount; i++ )
				arrayLinkValues.Add( 0.0 );
			Identifier = ID;
			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			this.log = log;
		}

		/// <summary>
		/// save the link details to a file
		/// </summary>
		public virtual void Save( XmlWriter xmlWriter )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Basic Link Save Called, link id no " + Identifier.ToString(), ClassName );
			}

			xmlWriter.WriteStartElement( "BasicLink" );
			xmlWriter.WriteElementString( "Identifier", Identifier.ToString() );
			for( int i=0; i<arrayLinkValues.Count; i++ )
			{
				xmlWriter.WriteElementString( "LinkValue", ( ( double )arrayLinkValues[ i ] ).ToString() );
			}
			xmlWriter.WriteElementString( "InputNodeID", bnInputNode.Identifier.ToString() );
			xmlWriter.WriteElementString( "OutputNodeID", bnOutputNode.Identifier.ToString() );
			xmlWriter.WriteEndElement();


		}

		/// <summary>
		/// load the link details from a file
		/// </summary>
		public virtual void Load( XmlReader xmlReader )
		{
			arrayLinkValues.Clear();

			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Basic Link Load Called, link id no " + Identifier.ToString(), ClassName );
			}

			while( xmlReader.Name != "Identifier" )
			{
				xmlReader.Read();
			}

			xmlReader.Read();
			Identifier = Int32.Parse( xmlReader.Value );

			while( xmlReader.Name != "LinkValue" )
			{
				xmlReader.Read();
			}

			xmlReader.Read();
			arrayLinkValues.Add( Double.Parse( xmlReader.Value ) );

			while( xmlReader.Name != "InputNodeID" )
			{
				xmlReader.Read();
			}

			xmlReader.Read();
			InputNodeID = Int32.Parse( xmlReader.Value );

			while( xmlReader.Name != "OutputNodeID" )
			{
				xmlReader.Read();
			}

			xmlReader.Read();
			OutputNodeID = Int32.Parse( xmlReader.Value );

		}

		/// <summary>
		/// Get the link value from the array list
		/// </summary>
		/// <param name="nID">number in the array</param>
		/// <returns>the value at the given point in the array or 0</returns>
		public virtual double GetLinkValue( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Link value for array number " + Identifier.ToString() + " link id no " + ID.ToString() + " value = " + ( ( double )arrayLinkValues[ nID ] ).ToString(), ClassName );
			}

			if( arrayLinkValues.Count == 0 || nID >= arrayLinkValues.Count )
				return 0.0;
				
			return ( double )arrayLinkValues[ nID ];
		}

		/// <summary>
		/// Set the given position in the array list to passed value
		/// </summary>
		/// <param name="dNewValue">new value</param>
		/// <param name="nID">number in the array</param>
		public virtual void SetLinkValue( double dNewValue, int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Setting the link value at " + Identifier.ToString() + " from " + ( ( double )arrayLinkValues[ nID ] ).ToString() + " to " + dNewValue.ToString() + " link id no " + ID.ToString(), ClassName );
			}

			if( arrayLinkValues.Count == 0 || nID >= arrayLinkValues.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Errors ) == true )
				{
					log.Log( DebugLevelSet.Warning, "Error the id " + Identifier.ToString() + " is greater than the number of link values or the link values array is empty, link id no " + ID.ToString(), ClassName );
				}
				return;
			}

			arrayLinkValues[ nID ] = dNewValue;
		}

		/// <summary>
		/// get and set the input node
		/// </summary>
		public BasicNode InputNode
		{
			get
			{
				return bnInputNode;
			}
			set
			{
				bnInputNode = value;
			}
		}

		/// <summary>
		/// get and set the output node
		/// </summary>
		public BasicNode OutputNode
		{
			get
			{
				return bnOutputNode;
			}
			set
			{
				bnOutputNode = value;
			}
		}


		/// <summary>
		/// Get the class name
		/// </summary>
		public string ClassName
		{
			get
			{
				return this.ToString();
			}
		}

		/// <summary>
		///  get the size of the array of link values
		/// </summary>
		public int Size 
		{
			get
			{
				return arrayLinkValues.Count;
			}
		}


		/// <summary>
		/// get the value of the input node at the given id
		/// </summary>
		/// <param name="nID">location of the input value</param>
		/// <returns>value stored at location ( nID )</returns>
		public virtual double InputValue( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Input Value at " + Identifier.ToString() + " link id no " + ID.ToString() + " value = " + InputNode.GetValue( nID ).ToString(), ClassName );
			}

			return InputNode.GetValue( nID );
		}

		/// <summary>
		/// get the value of the output node at the given id
		/// </summary>
		/// <param name="nID">location of the output value</param>
		/// <returns>value stored at location ( nID )</returns>
		public virtual double OutputValue( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Output Valuer at " + Identifier.ToString() + " link id no " + ID.ToString() + " value = " + OutputNode.GetValue( nID ).ToString(), ClassName );
			}

			return OutputNode.GetValue( nID );
		}

		/// <summary>
		/// get the value of the input error at the given id
		/// </summary>
		/// <param name="nID">location of the error at the given id</param>
		/// <returns>value stored at location ( nID )</returns>
		public virtual double InputError( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Input Error at " + Identifier.ToString()  + " link id no " + ID.ToString() + " value = " + ( ( double )InputNode.GetError( nID ) ).ToString(), ClassName );
			}

			return ( double )InputNode.GetError( nID );
		}

		/// <summary>
		/// get the value of the output error at the given id
		/// </summary>
		/// <param name="nID">location of the output error</param>
		/// <returns>value stored at the location ( nID )</returns>
		public virtual double OutputError( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Output Error at " + Identifier.ToString() + " linki id no " + ID.ToString() + " value = " + OutputNode.GetError( nID ).ToString(), ClassName );
			}

			return OutputNode.GetError( nID );
		}

		/// <summary>
		/// get the weighted input value at the given id
		/// </summary>
		/// <param name="nID">location of the input value</param>
		/// <returns>value of the number at the location ( nID ) multiplied by the weight</returns>
		public virtual double WeightedInputValue( int nID )
		{
			double dReturn = 0.0;
			if( Values.Weight < arrayLinkValues.Count )
				dReturn = bnInputNode.GetValue( nID ) * ( ( double )arrayLinkValues[ Values.Weight ] );
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "Warning the Values weight value is greater than the link values count returning 0.0, link id no " + ID.ToString(), ClassName );
				}
			}

			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Weighted Input Value at " + Identifier.ToString() + " link id no " + ID.ToString() + " unweighted value = " + bnInputNode.GetValue( nID ).ToString() + " weighted value = " + dReturn.ToString(), ClassName );
			}

			return dReturn;
		}

		/// <summary>
		/// get the weighted output value at the given id
		/// </summary>
		/// <param name="nID">location of the output value</param>
		/// <returns>value of the number at the location ( nID ) multiplied by the weight</returns>
		public virtual double WeightedOutputValue( int nID )
		{
			double dReturn = 0.0;
			if( Values.Weight < arrayLinkValues.Count )
				dReturn = bnOutputNode.GetValue( nID ) * ( ( double )arrayLinkValues[ Values.Weight ] );
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "Warning the Values Weight value is greater that the link values array count returning 0.0, link id no " + ID.ToString(), ClassName  );
				}
			}

			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Weighted Output value at " + Identifier.ToString() + " link id no " + ID.ToString() + " unweighted value = " + bnOutputNode.GetValue( nID ).ToString() + " weighted value = " + dReturn.ToString(), ClassName );
			}

			return dReturn;
		}

		/// <summary>
		/// get the wieghted output error at the given id
		/// </summary>
		/// <param name="nID">location of the output error</param>
		/// <returns>value of the number at the location ( nID ) multiplied by the weight</returns>
		public virtual double WeightedOutputError( int nID )
		{
			double dReturn = 0.0;
			if( Values.Weight < arrayLinkValues.Count )
				dReturn = bnOutputNode.GetError( nID ) * ( ( double )arrayLinkValues[ Values.Weight ] );
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "Warning the Values Weight value is greater that the link values array count returning 0.0, link id no " + Identifier.ToString(), ClassName );
				}
			}
			
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Weighted Output Error value at " + Identifier.ToString() + " link id no " + Identifier.ToString() + " unweighted value = " + bnOutputNode.GetError( nID ).ToString() + " weighted value = " + dReturn.ToString(), ClassName );
			}
 


			return dReturn;
		}

		/// <summary>
		/// get the weighted input error at the given id
		/// </summary>
		/// <param name="nID">location of the input error</param>
		/// <returns>value of the number at the location ( nID ) multiplied by the wieght</returns>
		public virtual double WeightedInputError( int nID )
		{
			double dReturn = 0.0;
			if( Values.Weight < arrayLinkValues.Count )
				dReturn = bnInputNode.GetError( nID ) * ( ( double )arrayLinkValues[ Values.Weight ] );
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "Warning the Values Weight value is greater than the link values array count returning 0.0, link id no " + Identifier.ToString(), ClassName );
				}
			}

			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the Weighted Input Error value at " + Identifier.ToString() + " link id no " + Identifier.ToString() + " unweighted value = " + bnInputNode.GetError( nID ).ToString() + " weighted value = " + dReturn.ToString(), ClassName );
			}


			return dReturn;
		}

		/// <summary>
		///  Update the weight for this basic Link
		/// </summary>
		/// <param name="dNewValue">new weight value for the link</param>
		public virtual void UpdateWeight( double dNewValue )
		{

			if( Values.Weight < arrayLinkValues.Count )
			{
				double dTemp = ( double )arrayLinkValues[ Values.Weight ];
				dTemp += dNewValue;

				if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
				{
					log.Log( DebugLevelSet.Progress, "Updating the Weight for this basic link id no " + Identifier.ToString() + " from = " + ( ( double )arrayLinkValues[ Values.Weight ]).ToString() + " to = " + dTemp.ToString(), ClassName );
				}

				arrayLinkValues[ Values.Weight ] = dTemp;
			}
		}
	}

	/// <summary>
	/// Generic definition for an bias node
	/// </summary>
	public class Bias
	{
		private DebugLevel debugLevel;
		private Logger log;
		private ArrayList arrayBiasValues;


		/// <summary>
		/// basic constructor taking only the logger
		/// </summary>
		/// <param name="log">xml log file that info is written too</param>
		public Bias( Logger log )
		{
			this.log = log;
			arrayBiasValues = new ArrayList();
			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			arrayBiasValues.Add( 1.0 );
		}

		/// <summary>
		/// constructor taking the logger and the bias value ( rarely used as bias is usually 1.0 )
		/// </summary>
		/// <param name="log">xml log file that info is written too</param>
		/// <param name="dBias">bias to be applied</param>
		public Bias( Logger log, double dBias ) 
		{
			arrayBiasValues = new ArrayList();

			if( dBias == 0.0 )
				dBias = 1.0;

			arrayBiasValues.Add( dBias );
			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			this.log = log;

		}

		/// <summary>
		/// get the original bias
		/// </summary>
		/// <returns>bias value normally one</returns>
		public double GetOriginalBias()
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Returning original bias of " + arrayBiasValues[ 0 ].ToString(), ClassName );
			}

			return ( double )arrayBiasValues[ 0 ];
		}

		/// <summary>
		/// get the bias at a given id
		/// </summary>
		/// <param name="nID">location of the bias value</param>
		/// <returns>value at the given location ( nID )</returns>
		public double GetBiasAt( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Setting the Bias at " + nID.ToString() + " to " + ( ( double )arrayBiasValues[ 0 ] ).ToString(), ClassName );
			}

			if( nID < arrayBiasValues.Count )
			{
				return ( double )arrayBiasValues[ nID ];
			}
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning the id passed to the array for getting the bias " + nID.ToString() + " is greater than the number of elements " + arrayBiasValues.Count.ToString() );
				}
			}

			return 0.0;
		}


		/// <summary>
		/// set the bias value at the default location
		/// </summary>
		/// <param name="dBias">bias value to set</param>
		public void SetBias( double dBias )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Setting the bias from " + ( ( double )arrayBiasValues[ 0 ] ).ToString() + " to " + dBias.ToString() );
			}

			arrayBiasValues[ 0 ] = dBias;
		}


		/// <summary>
		/// set the bias value at the given location
		/// </summary>
		/// <param name="nID">location of the bias value to set</param>
		/// <param name="dBias">new bias value</param>
		public void SetBiasAt( int nID, double dBias )
		{
			if( nID < arrayBiasValues.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
				{
					log.Log( DebugLevelSet.Progress, "Setting the bias at " + nID.ToString() + " from " + ( ( double )arrayBiasValues[ nID ] ).ToString() + " to " + dBias.ToString() );
				}

				arrayBiasValues[ nID ] = dBias;
			}
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning the id passed to the array for setting the bias " + nID.ToString() + " is greater than the number of elements " + arrayBiasValues.Count.ToString() );
				}
			}
		}


		/// <summary>
		/// get the class name
		/// </summary>
		public string ClassName
		{
			get
			{
				return this.ToString();
			}
		}

		/// <summary>
		/// save the current bias node
		/// </summary>
		/// <param name="xmlWriter">xml writer to the save file</param>
		public virtual void Save( XmlWriter xmlWriter )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Save called in bias node", ClassName );
			}

			xmlWriter.WriteStartElement( "Bias" );
			for( int i=0; i<arrayBiasValues.Count; i++ )
			{
				xmlWriter.WriteElementString( "BiasValue", ( ( double )arrayBiasValues[ i ] ).ToString() );
			}
			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// reload the saved bias node
		/// </summary>
		/// <param name="xmlReader">xml reader to the file load the data from</param>
		public virtual void Load( XmlReader xmlReader )
		{
			while( xmlReader.Name != "Bias" )
			{
				xmlReader.Read();
			}

			bool bBreak = false;
			for( ;; )
			{
				switch( xmlReader.NodeType )
				{
					case XmlNodeType.Element:
					{
						switch( xmlReader.Name )
						{
							case "BiasValue":
							{
								xmlReader.Read();
								arrayBiasValues.Add( Double.Parse( xmlReader.Value ) ); break;
							}
						}
					} break;
					case XmlNodeType.EndElement:
					{
						switch( xmlReader.Name )
						{
							case "Bias": bBreak = true; break;
						}
					} break;
				}

				if( bBreak == true )
					break;

				xmlReader.Read();
			}
		}
	}

	/// <summary>
	/// basic node class, form the base class for all nodes in all networks
	/// </summary>
	public class BasicNode : Basic
	{
		private ArrayList arrayNodeValues; /// double values
		private ArrayList arrayNodeErrors; /// double values
		private ArrayList arrayInputLinks; /// Basic links 
		private ArrayList arrayOutputLinks; /// Basic links

		private DebugLevel debugLevel;
		private Logger log;

		/// <summary>
		/// use the bias or not default not. The bias is static as it is applied to the
		/// network as a whole and not to individual nodes
		/// </summary>
		private static bool bUseBias = false;

		private Bias bias;

		private int nIdentifier;

		/// <summary>
		/// node id
		/// </summary>
		public int Identifier
		{
			get
			{
				return nIdentifier;
			}
			set
			{
				nIdentifier = value;
			}
		}

		/// <summary>
		/// set the optional bias
		/// </summary>
		public bool UseBias
		{
			get
			{
				return bUseBias;
			}
			set
			{
				bUseBias = value;
			}
		}


		/// <summary>
		/// get the current bias
		/// </summary>
		public Bias GetBias
		{
			get
			{
				return bias;
			}
		}

		/// <summary>
		/// most basic constructor only takes the log file object
		/// </summary>
		/// <param name="slog">log file for storing info</param>
		public BasicNode( Logger slog )
		{
			bias = new Bias( slog );
			arrayNodeValues = new ArrayList();
			arrayNodeValues.Add( 0.0 );
			arrayNodeErrors = new ArrayList();
			arrayNodeErrors.Add( 0.0 );
			arrayInputLinks = new ArrayList();
			arrayOutputLinks = new ArrayList();
			Identifier = ID;
			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			log = slog;
		}

		/// <summary>
		/// constructor taking the log and the sizes for the values and errors
		/// </summary>
		/// <param name="slog">log file for writing info</param>
		/// <param name="nodeValuesSize">initial size of the values array</param>
		/// <param name="nodeErrorsSize">initial size of the errors array</param>
		public BasicNode( Logger slog, int nodeValuesSize, int nodeErrorsSize )
		{
			bias = new Bias( slog );
			arrayNodeValues = new ArrayList( nodeValuesSize );
			for( int i=0; i<nodeValuesSize; i++ )
				arrayNodeValues.Add( 0.0 );
			arrayNodeErrors = new ArrayList( nodeErrorsSize );
			for( int i=0; i<nodeErrorsSize; i++ )
				arrayNodeErrors.Add( 0.0 );
			arrayInputLinks = new ArrayList();
			arrayOutputLinks = new ArrayList();
			Identifier = ID;
			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			log = slog;
		}

		/// <summary>
		/// values stored for this node
		/// </summary>
		public ArrayList NodeValues
		{
			get
			{
				return arrayNodeValues;
			}
		}

		/// <summary>
		/// error values for this node
		/// </summary>
		public ArrayList NodeErrors
		{
			get
			{
				return arrayNodeErrors;
			}
		}

		/// <summary>
		/// input links for this node
		/// </summary>
		public ArrayList InputLinks
		{
			get
			{
				return arrayInputLinks;
			}
		}

		/// <summary>
		/// output links for this node
		/// </summary>
		public ArrayList OutputLinks
		{
			get
			{
				return arrayOutputLinks;
			}
		}

		/// <summary>
		/// run this node ( no implementation as it is meant to be overridden
		/// </summary>
		/// <param name="nMode"></param>
		public virtual void Run( int nMode )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Run called in basic node", ClassName );
			}
		}

		/// <summary>
		/// run this node ( no implementation as it is meant to be overridden ) ( pretty much obsolete already )
		/// </summary>
		/// <param name="nMode"></param>
		/// <param name="dBias"></param>
		public virtual void Run( int nMode, double dBias )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, " Run with bias called in basic node", ClassName );
			}
		}

		/// <summary>
		/// run the learning algorithm for the node ( no implementation as it is meant to be overridden
		/// </summary>
		/// <param name="nMode"></param>
		public virtual void Learn( int nMode )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Learn Called in basic node", ClassName );
			}
		}


		/// <summary>
		/// save the current node
		/// </summary>
		/// <param name="xmlWriter">xmlWriter to the file to write too</param>
		public virtual void Save( XmlWriter xmlWriter )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Save called in basic node", ClassName );
			}

			xmlWriter.WriteStartElement( "BasicNode" );
			xmlWriter.WriteElementString( "Identifier", Identifier.ToString() );
			for( int i=0; i<arrayNodeValues.Count; i++ )
			{
				xmlWriter.WriteElementString( "NodeValue", ( ( double )arrayNodeValues[ i ] ).ToString() );
			}
			for( int i=0; i<arrayNodeErrors.Count; i++ )
			{
				xmlWriter.WriteElementString( "NodeError", ( ( double )arrayNodeErrors[ i ] ).ToString() );
			}

			bias.Save( xmlWriter );

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// reload the node
		/// </summary>
		/// <param name="xmlReader">xml reader to the file to read from</param>
		public virtual void Load( XmlReader xmlReader )
		{
			arrayNodeValues.Clear();
			arrayNodeErrors.Clear();
			arrayInputLinks.Clear();
			arrayOutputLinks.Clear();

			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Load called in basic node", ClassName );
			}

			while( xmlReader.Name != "Identifier" )
			{
				xmlReader.Read();
			}

			xmlReader.Read();
			Identifier = Int32.Parse( xmlReader.Value );

			bool bBreak = false;
			/// can be any number of node values not just one
			for( ;; )
			{
				xmlReader.Read();
				switch( xmlReader.NodeType )
				{
					case XmlNodeType.Element:
					{
						switch( xmlReader.Name )
						{
							case "NodeValue":
							{
								xmlReader.Read();
								arrayNodeValues.Add( Double.Parse( xmlReader.Value ) ); break;
							}
							case "NodeError": bBreak = true; break;
						}
					} break;
				}

				if( bBreak == true )
					break;
			}

			/// should now be on Node error
			bBreak = false;
			for( ;; )
			{
				switch( xmlReader.NodeType )
				{
					case XmlNodeType.Element:
					{
						switch( xmlReader.Name )
						{
							case "NodeError":
							{
								xmlReader.Read();
								if( xmlReader.Value == "0" )
									arrayNodeErrors.Add( 0.0 );
								else if( xmlReader.Value == "1" )
									arrayNodeErrors.Add( 1.0 );
								else
									arrayNodeErrors.Add( Double.Parse( xmlReader.Value ) ); break;
							}
						}
					} break;
					case XmlNodeType.EndElement:
					{
						switch( xmlReader.Name )
						{
							case "NodeError": bBreak = true; break;
						}
					} break;
				}

				if( bBreak == true )
					break;

				xmlReader.Read();
			}

			bias.Load( xmlReader );

			/// links data is not loaded here as the link contains all the needed information
		}

		/// <summary>
		/// get the value from the array node values at the given id
		/// </summary>
		/// <param name="nID">location of the value ( nID )</param>
		/// <returns>value at the given location ( nID )</returns>
		public virtual double GetValue( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Get value called in basic node", ClassName );
			}

			if( arrayNodeValues.Count == 0 || nID >= arrayNodeValues.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "The id passed to basic node " + Identifier.ToString() + " is greater than the array node values count, returning 0.0", ClassName );
				}

				return 0.0;
			}

			return ( double )arrayNodeValues[ nID ];
		}

		/// <summary>
		/// set the value at the given id 
		/// </summary>
		/// <param name="nID">location of the value to set</param>
		/// <param name="dNewValue">value to be set too</param>
		public virtual void SetValue( int nID, double dNewValue )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Setting the value for id " + Identifier.ToString() + " to " + dNewValue.ToString(), ClassName );
			}

			if( arrayNodeValues.Count == 0 || nID >= arrayNodeValues.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "The id passed to basic node setvalue is greater than the array count ", ClassName );
				}

				return;
			}

			arrayNodeValues[ nID ] = dNewValue;
		}

		/// <summary>
		/// get the error value at the passed in id
		/// </summary>
		/// <param name="nID">location of the error to get</param>
		/// <returns>value of the error at the given location ( nID )</returns>
		public virtual double GetError( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Getting the error value at " + Identifier.ToString() + " for basic node", ClassName );
			}

			if( arrayNodeErrors.Count == 0 || nID >= arrayNodeErrors.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "The id passed to basic node get error is greater than the array count returning 0.0", ClassName );
				}

				return 0.0;
			}
		
			return ( double )arrayNodeErrors[ nID ];
		}

		/// <summary>
		/// set the error at the given id
		/// </summary>
		/// <param name="nID">location of the error to set</param>
		/// <param name="dNewValue">value of the new error</param>
		public virtual void SetError( int nID, double dNewValue )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Setting the error value for basic node at " + Identifier.ToString() + " to " + dNewValue.ToString(), ClassName );
			}

			if( arrayNodeErrors.Count == 0 || nID >= arrayNodeErrors.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.Warning ) == true )
				{
					log.Log( DebugLevelSet.Warning, "The id passed to basic node set error is greater than the array count", ClassName );
				}

				return;
			}


			arrayNodeErrors[ nID ] = dNewValue;
		}	

		/// <summary>
		/// get the class name
		/// </summary>
		public string ClassName
		{
			get
			{
				return this.ToString();
			}
		}

		/// <summary>
		/// create a link to a node
		/// </summary>
		/// <param name="bnToNode">node to create a link to</param>
		/// <param name="blLink">Basic link object to create the link</param>
		public void CreateLink( BasicNode bnToNode, BasicLink blLink )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Creating a link between node " + bnToNode.Identifier.ToString() + " and link " + blLink.Identifier.ToString(), ClassName );
			}

			arrayOutputLinks.Add( blLink );
			bnToNode.InputLinks.Add( blLink );
			blLink.InputNode = this;
			blLink.OutputNode = bnToNode;
		}


		/// <summary>
		/// disconnect a connection from the arrays
		/// </summary>
		/// <param name="bnFromNode">node to disconnect from</param>
		/// <param name="bnToNode">node connected to</param>
		/// <returns>true on success</returns>
		public bool Disconnect( BasicNode bnFromNode, BasicNode bnToNode )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Disconnecting the connection between " + bnFromNode.ID.ToString() + " to " + bnToNode.ID.ToString(), ClassName );
			}

			ArrayList tempList = bnFromNode.OutputLinks;
			bool bFound = false;
			/// see if it is possible to find the connection to the to node
			int n=0;
			for( ; n<tempList.Count; n++ )
			{
				if( ( ( BasicLink )tempList[ n ] ).OutputNode.Equals( bnToNode ) == true )
				{
					bFound = true;
					break;
				}
			}

			if( bFound == true )
			{
				/// from the current node remove the input then remove the node
				( ( BasicLink )tempList[ n ] ).OutputNode.arrayInputLinks.Remove( tempList[ n ] );
				tempList.RemoveAt( n );

				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// print out info
		/// </summary>
		public virtual void Print()
		{
		}

	}


	/// <summary>
	/// the basic network class
	/// Base class for most networks
	/// </summary>
	public abstract class BasicNetwork 
	{
		private ArrayList arrayNodes; /// array of basicnodes
		private ArrayList arrayLinks;

		private DebugLevel debugLevel;
		private Logger log;

		private double dLearningRate;

		/// <summary>
		/// current learning rate
		/// </summary>
		public double LearningRate
		{
			get
			{
				return dLearningRate;
			}
			set
			{
				dLearningRate = value;
			}
		}

		/// <summary>
		/// allow the code to set if the bias should be used
		/// </summary>
		public bool UseBias
		{
			set
			{
				if( arrayNodes.Count > 0 )
				{
					( ( BasicNode )arrayNodes[ 0 ] ).UseBias = value;
				}
			}
		}

		/// <summary>
		/// most basic constructor taking only the logger
		/// </summary>
		/// <param name="log">log file object</param>
		public BasicNetwork( Logger log )
		{
			arrayNodes = new ArrayList();
			arrayLinks = new ArrayList();

			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			this.log = log;

		}


		/// <summary>
		/// constructor taking the logger object the number of nodes and the number of links
		/// </summary>
		/// <param name="log">logger</param>
		/// <param name="nNodesCount">number of nodes to create</param>
		/// <param name="nLinksCount">number of links to create</param>
		public BasicNetwork( Logger log, int nNodesCount, int nLinksCount )
		{
			arrayNodes = new ArrayList( nNodesCount );
			arrayLinks = new ArrayList( nLinksCount );

			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			this.log = log;

		}

		/// <summary>
		/// add a new node to the network
		/// </summary>
		/// <param name="node">node to be added</param>
		public virtual void AddNode( BasicNode node )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Adding a new neuron to basic network" );
			}

			arrayNodes.Add( node );
		}


		/// <summary>
		/// add a new link to the network
		/// </summary>
		/// <param name="link">link to be added</param>
		public virtual void AddLink( BasicLink link )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Adding a new link to the basic network" );
			}

			arrayLinks.Add( link );
		}


		/// <summary>
		/// remove the node at the specified location
		/// </summary>
		/// <param name="nID">location of the node to be removed</param>
		public virtual void RemoveNodeAt( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Removing a node from basic network at " + nID.ToString() );
			}

			if( nID <= arrayNodes.Count )
			{
				arrayNodes.RemoveAt( nID );
			}
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning attempt to remove a node from basic network " + nID.ToString() + " when there are only " + arrayNodes.Count.ToString() + " in the array " );
				}
			}
		}

		/// <summary>
		/// remove the link at the specified location
		/// </summary>
		/// <param name="nID">location of the link to be removed</param>
		public virtual void RemoveLinkAt( int nID )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Removing a link from the basic network at " + nID.ToString() );
			}

			if( nID <= arrayNodes.Count )
			{
				arrayNodes.RemoveAt( nID );
			}
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning attemot to remove a node from basic network " + nID.ToString() + " when there are only " + arrayLinks.Count.ToString() + " in the array " );
				}
			}
		}

		/// <summary>
		/// get the node at the given location
		/// </summary>
		/// <param name="nID">location of the node to retreive</param>
		/// <returns>node at location ( nID )</returns>
		public BasicNode GetNodeAt( int nID )
		{
			if( arrayNodes.Count >= nID )
				return ( BasicNode )arrayNodes[ nID ];
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning attempt to get a node from basic network " + nID.ToString() + " when there are only " + arrayNodes.Count.ToString() + " in the array " );
				}
			}

			return null;
		}

		/// <summary>
		/// ge the link at the given location
		/// </summary>
		/// <param name="nID">location of the link to retreive</param>
		/// <returns>link at location ( nID )</returns>
		public BasicLink GetLinkAt( int nID )
		{
			if( arrayLinks.Count >= nID )
				return ( BasicLink )arrayLinks[ nID ];
			else
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning attempt to get a link from basic network " + nID.ToString() + " when there are only " + arrayLinks.Count.ToString() + " in the array " );
				}
			}

			return null;
		}

		/// <summary>
		/// allow access to the Nodes
		/// </summary>
		public ArrayList Nodes
		{
			get
			{
				return arrayNodes;
			}
		}

		/// <summary>
		/// allow access to the links
		/// </summary>
		public ArrayList Links
		{
			get
			{
				return arrayLinks;
			}
		}

		protected abstract void CreateNetwork();


		/// <summary>
		/// save the current network
		/// </summary>
		/// <param name="xmlWriter">sml writer for the file to save to</param>
		public virtual void Save( XmlWriter xmlWriter )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Save Node Links called for basic network ", ClassName );
			}

			xmlWriter.WriteStartElement( "BasicNetwork" );
			
			for( int i=0; i<arrayNodes.Count; i++ )
			{
				( ( BasicNode )arrayNodes[ i ] ).Save( xmlWriter );
			}

			for( int i=0; i<arrayLinks.Count; i++ )
			{
				( ( BasicLink )arrayLinks[ i ] ).Save( xmlWriter );
			}

			xmlWriter.WriteEndElement();
		}

		/// <summary>
		/// example of the load function. Can't implement load here as the basic Link class is abstract
		/// </summary>
		/// <param name="xmlReader"></param>
		public virtual void Load( XmlReader xmlReader )
		{
			/*
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Load Node links called for basic network", ClassName );
			}

			arrayNodes.Clear();
			arrayLinks.Clear();

			/// can be any number of basic nodes not just one
			for( ;; )
			{
				xmlReader.Read();
				switch( xmlReader.NodeType )
				{
					case XmlNodeType.Element:
					{
						switch( xmlReader.Name )
						{
							case "BasicNode":
							{
								BasicNode temp = new BasicNode( log );
								temp.Load( xmlReader );
								arrayNodes.Add( temp );
							}
							case "BasicLink": bBreak = true; break;
						}
					} break;
				}

				if( bBreak == true )
					break;
			}

			/// should now be on Basic Link
			bBreak = false;
			for( ;; )
			{
				switch( xmlReader.NodeType )
				{
					case XmlNodeType.Element:
					{
						switch( xmlReader.Name )
						{
							case "BasicLink":
							{
								BasicLink temp = new BasicLink();
								temp.Load( xmlReader );
								arrayLinks.Add( temp );
							}
						}
					} break;
					case XmlNodeType.EndElement:
					{
						switch( xmlReader.Name )
						{
							case "BasicNetwork": bBreak = true; break;
						}
					} break;
				}

				if( bBreak == true )
					break;

				xmlReader.Read();
			}
			*/
		}


		public string ClassName
		{
			get
			{
				return this.ToString();
			}
		}


		/// <summary>
		/// perform a series of operations on the nodes ( usually all of them )
		/// </summary>
		/// <param name="nMode"></param>
		public virtual void Epoch()
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "Epoch called for basic network ", ClassName );
			}
		}

		/// <summary>
		/// set the value for the node at the given index
		/// </summary>
		/// <param name="nID"></param>
		/// <param name="dNewVal"></param>
		public virtual void SetValue( int nID, double dNewVal )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "set value called for basic network ", ClassName );
			}

			if( nID >= this.Nodes.Count )
			{
				if( debugLevel.TestDebugLevel( DebugLevelSet.WarningsAndErrors ) == true )
				{
					log.Log( DebugLevelSet.WarningsAndErrors, "Warning the id passed to set value in Basic network is higher than the node count, returning without setting the value", ClassName );
				}

				return;
			}

			this.GetNodeAt( nID ).SetValue( Values.NodeValue, dNewVal );
		}


		/// <summary>
		/// set the values for the nodes to the values in the pattern
		/// </summary>
		/// <param name="pattern">a Pattern derived class containing the input values for the nodes</param>
		public virtual void SetValue( Pattern pattern )
		{
			if( debugLevel.TestDebugLevel( DebugLevelSet.Progress ) == true )
			{
				log.Log( DebugLevelSet.Progress, "set value called for basic network ", ClassName );
			}

			for( int i=0; i<pattern.InSet.Count; i++ )
			{
				this.GetNodeAt( i ).SetValue( Values.NodeValue, pattern.InputValue( i ) );
			}
		}
	}



	/// <summary>
	/// The basic neuron class models the minimum needed for a neuron and is abstract to force inheritance 
	/// so that each network can add what is required.
	/// </summary>
	public abstract class BasicNeuron
	{
		private BasicNode basicNodeInputNodeOne;
		private BasicNode basicNodeInputNodeTwo;

		private DebugLevel debugLevel;
		private Logger log;

		public BasicNode InputNodeOne
		{
			get
			{
				return basicNodeInputNodeOne;
			}
			set
			{
				basicNodeInputNodeOne = value;
			}
		}

		public BasicNode InputNodeTwo
		{
			get
			{
				return basicNodeInputNodeTwo;
			}
			set
			{
				basicNodeInputNodeTwo = value;
			}
		}



		/// <summary>
		/// constructor
		/// </summary>
		/// <param name="log"></param>
		/// <param name="basicInputNodeOne"></param>
		/// <param name="basicInputNodeTwo"></param>
		/// <param name="biasBiasNode"></param>
		public BasicNeuron( Logger log, BasicNode basicInputNodeOne, BasicNode basicInputNodeTwo )
		{
			InputNodeOne = basicInputNodeOne;
			InputNodeTwo = basicInputNodeTwo;

			debugLevel = new DebugLevel( DebugLevel.currentLevel );
			this.log = log;
		}


		/// <summary>
		/// each neuron will need to define how to build it's own links
		/// </summary>
		public abstract void BuildLinks();


		public string ClassName
		{
			get
			{
				return this.ToString();
			}
		}

		public string Data
		{
			get
			{
				StringBuilder strString = new StringBuilder();
				strString.Append( "Basic Neuron Values : Input Node One Values = " );
				for( int i=0; i<InputNodeOne.NodeValues.Count; i++ )
				{
					strString.Append( " value " + i.ToString() + " = " + InputNodeOne.NodeValues[ i ].ToString() );
				}
				strString.Append( " : Node Errors = "  );
				for( int i=0; i<InputNodeOne.NodeErrors.Count; i++ )
				{
					strString.Append( " value " + i.ToString() + " = " + InputNodeOne.NodeErrors[ i ].ToString() );
				}
				strString.Append( " : Node Input Links = " );
				for( int i=0; i<InputNodeOne.InputLinks.Count; i++ )
				{
					strString.Append( " value " + i.ToString() + " = " + InputNodeOne.InputLinks[ i ].ToString() );
				}
				strString.Append( " : Node Output Links = " );
				for( int i=0; i<InputNodeOne.OutputLinks.Count; i++ )
				{
					strString.Append( " value " + i.ToString() + " = " + InputNodeOne.OutputLinks[ i ].ToString() );
				}
			
				return strString.ToString();
			}

		}

		/// <summary>
		/// save the current data
		/// </summary>
		/// <param name="xmlWriter"></param>
		public virtual void Save( XmlWriter xmlWriter )
		{
			xmlWriter.WriteStartElement( "BasicNeuron" );
			basicNodeInputNodeOne.Save( xmlWriter );
			basicNodeInputNodeTwo.Save( xmlWriter );
			xmlWriter.WriteEndElement();
		}


		/// <summary>
		/// reload saved data
		/// </summary>
		/// <param name="xmlReader"></param>
		public virtual void Load( XmlReader xmlReader )
		{
			while( xmlReader.Name != "BasicNode" )
			{
				xmlReader.Read();
			}

			basicNodeInputNodeOne.Load( xmlReader );

			while( xmlReader.Name != "BasicNode" )
			{
				xmlReader.Read();
			}

			basicNodeInputNodeTwo.Load( xmlReader );

		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions