Each create method of pvAccces::Channel has an argument PVStructure pvRequest. This document discusses how this argument is defined and used.
This product is available via an open source license
This document is for related set of products:
Each of the products has a Java and a C++ implementation.
The products are all part of the V4 implementation of Experimental Physics and Industrial Control System.
This is the 01-Mar-2013 version of the the description of pvRequest.
pvAccess defines interface Channel which has a number of create methods, which have a argument pvRequest:
ChannelProcess createChannelProcess( ChannelProcessRequester channelProcessRequester, PVStructure pvRequest); ChannelGet createChannelGet( ChannelGetRequester channelGetRequester, PVStructure pvRequest); ChannelPut createChannelPut( ChannelPutRequester channelPutRequester, PVStructure pvRequest); ChannelPutGet createChannelPutGet( ChannelPutGetRequester channelPutGetRequester, PVStructure pvRequest); ChannelRPC createChannelRPC( ChannelRPCRequester channelRPCRequester, PVStructure pvRequest); ChannelArray createChannelArray( ChannelArrayRequester channelArrayRequester, PVStructure pvRequest); Monitor createMonitor( MonitorRequester MonitorRequester, PVStructure pvRequest); }
A Channel is a connection between a client and a server. For each create method it is the server that determines how pvRequest should be defined. The client is expected to know what the server expects. If the client passes an illegal pvRequest to the server then the server will respond to the create method with an error message.
pvAcccess does not define the format of a pvRequest structure but it does provide a convenience method:
PVStructure createRequest(String request, Requester requester);This creates a pvRequest structure that can be used to communicate with a server that supports the pcIOC Data Model as defined in the next section.
The pvdata project also defines a standard set of normative types, which are used to pass data between client and server. The pcIOC Data Model supports the general normative types NTScaler, NTScalarArrray, and NTEnum Other normative types can define their own convenience methods for creating pvRequest structures to pass between client and server The other normative types will not be discussed in this document.
This document is based on material that is currently defined in pvDataJava.html, pvAccessJava.html, and pvIOCJava.html. This document makes pvRequest easier to understand by bringing all the concepts into a single document.
The Data Model supports general purpose client tools. A set of structures are defined that support the data model. A pvAccess server supports the data model if it follows a few simple guidelines.
The model can briefly be described as follows: Clients access a field named value, which may be a field in the top level structure of a record or in a substructure. All other fields in the structure are considered properties of the value field. The fieldname is the property name. The value is usually a data field, i.e. a scalar type or an array with the elementType being scalar. All other fields of the structure support the value. The standard property fields are timeStamp, alarm, display, and control.
A structure may also have fields other than the standard property fields but these other fields are normally not of interest to clients and are not used by general purpose client tools. These additiional fields are normally used by support code that gets called as part of record processing.
For example the following record has a single value field. The value field has property fields timeStamp, alarm, and display. It also has a field valueAlarm which is not of interest to most clients.
record scalarDouble double value 0.0 time_t 1969-12-31 19:00:00.000 userTag 0 alarm_t alarm severity NONE status NONE message display_t display double limitLow 0.0 double limitHigh 0.0 string description Sample Description string format %f string units volts valueAlarm_t valueAlarm boolean active true double lowAlarmLimit 2.0 double lowWarningLimit 4.0 double highWarningLimit 8.0 double highAlarmLimit 10.0 int lowAlarmSeverity 2 int lowWarningSeverity 1 int highWarningSeverity 1 int highAlarmSeverity 2 double hystersis 0.2
The following example has three value fields: voltage.value, current.value, and power.value The record itself has properties alarm and timeStamp voltage.value and power.value have no property fields. current.value has a property fields alarm and display.
record psSimple alarm_t alarm severity MAJOR status RECORD message highAlarm time_t 2013-02-25 10:07:46.529 userTag 0 structure voltage double value 1.0 structure current double value 10.0 alarm_t alarm severity MAJOR status RECORD message highAlarm display_t display double limitLow -10.0 double limitHigh 10.0 string description string format string units current structure power double value 10.0
A general purpose client tool might ask for power.value, current.value, or voltage.value. A more specialized client might be interested in the entire record.
This data model supports existing EPICS V3 channel access clients. More powerful or special purpose clients can handle structured data. Such clients can access a structure. For example a client can ask for an entire record. An example is swtshell.
A structure follows the pvIOC Data Model if it has a set of fields that have names and types that support the data model. It can have additional fields, that can also be considered properties. The following field names have special meaning, i.e. are properties for general purpose clients.
In addition the javaIOC structure can have additional fields that support the value field but are not recognized by most general purpose client tools. Typical examples are:
The model allows for device records. A device record has fields that are structures that support the javaIOC data model. For example a power supply record can have fields power, voltage, current that each support the javaIOC data model.
The set of structures for standard properties for general purpose client tools are defined in pvData. These are:
structure alarm int severity int status string messageAn alarm also has associated convenience code for processing and displaying alarms. In particular the convenience code provides methods that treat severity and status like enumerated values.
structure timeStamp long secondsPastEpoch int nanoSeconds int userTagA timeStamp also has associated convenience code for processing and displaying timeStamps..
structure display double limitLow double limitHigh string description string format string units
structure control double limitLow double limitHigh double minStep
See the documentation for either pvDataJava or pvDataCPP for more details about property fields.
pvAccess (both pvAccessJava and pvAccessCPP) have a convenience class with a method createRequest. For example pvAcccessJava has:
public class CreateRequestFactory { public static PVStructure createRequest(String request,Requester requester) { return CreateRequestImpl.createRequest(request,requester); } }
The purpose is to create request structures for accesssing records in a server that supports the pvIOC data model. It allows the caller to specify the following:
NOTE: The term record is adapted from pvIOC. A pvIOC has a memory resident database of smart records. A pvAcccess channel is a connection to a record. A record has a top level PVStructure that holds the data for the record. A pvAcccess server can be implemented that does not use the pvIOC model but it must provide top level PVStructures to which a pvAcccess Channel can be attached. If the server uses that pvIOC Data Model then createRequest can also be used for this type of server.
A request argument has the syntax:
record[option,...]field(fieldDef,...)putField(fieldDef,...)getField(fieldDef,...) OR fieldDef,...
Thus a request consists of record options and sets of field definitions or just field definitions. A record option is of the form:
record[name=value,...]
A field,putFeld,getField is a comma separated set of fieldDefs which are of the form:
fullFieldName or fullFieldName[option,...] or fullFieldName{request} // recursive definition
A fullFieldName is the full name of a field in the PVRecord. The name in a generated data structure will have just the field name. If fieldName{request} is given then the generated data structure will have a structure field with subfields. Note that request is a recursive definition.
If request is null or an empty string than the entire PVRecord is selected.
The syntax was chosen to be easy to use and to parse:
A Java client can create a request as follows:
PVStructure pvRequest = CreateRequestFactory.createRequest(request,requester);
NOTE: The full path is org.epics.pvaccess.client.CreateRequestFactory.
A C++ client calls:
epics::pvData::PVStructure::shared_pointer pvRequest = getCreateRequest()->createRequest(request,requester);
For both Java and C++:
Clients like CSS, Synoptic Displays, Alarm, and Archive tools only want access to some combination of the following fields: value, alarm, timeStamp, display, and control. If the request is for a record that has these all as top level fields the request string is just a comma separated list of the field names. For example:
"value,alarm,timeStamp"
If the record is does not have the desired field at the top level then the field can still be accessed. For example:
"alarm,timeStamp,power.value"
Will get the top level alarm and timeStamp and the value from a structure named power. Thus the above works for a record that is structured as follows:
powerSupply alarm timeStamp power value display ... ...
The only option most clients want is to request that a record be processed as part of a get or put requests. This is done via requests like the following:
"record[process=true]field(value,alarm,timeStamp)"
The following examples are for a power supply record:
powerSupply alarm timeStamp power value alarm .. other fields voltage value alarm .. other fields current value alarm .. other fields .. other fields
The following request:
"field(alarm,timeStamp,power.value)"
Will return to the client the following:
record psSimple alarm_t alarm severity NONE status NONE message null time_t 2013-02-27 06:04:30.997 userTag 0 double value 10.0
Note that if the actual record does not have a requested field than it will not be present in the structure returned to the client.
The following is the same except that a record option and an option for the value field will be given.
"record[process=true]field(alarm,timeStamp,power.value[shareData=true])"
The options are to process the record and to share the value data with the power.value field in the PVRecord.
The following:
"field(alarm,timeStamp,power{value,alarm},current{value,alarm},voltage{value,alarm})"
Will return to the client the following:
record psEmbeded alarm_t alarm severity NONE status NONE message time_t 1969-12-31 19:00:00.000 userTag 0 structure power double value 0.0 structure current double value 0.0 alarm_t alarm severity NONE status NONE message structure voltage double value 0.0 alarm_t alarm severity NONE status NONE message
This section describes how org.epics.pvioc.pvAccess.ChannelServerFactory implements the create methods of the pvAccess Channel interface. This is the code that allows a client to access records in a pvIOC. In particular it describes how it interpets the PVRequest request argument of the various create methods. Thus section can also be used as an example for other pvAccess servers.
The various channel methods use pvCopy and monitor which are currently only implemented by pvIOCJava. These are briefly described in the next two sub-sections. A longer discussion is given in the last two sections of this document. After reading this section quickly it may be a good idea to read the last two sections and then come back and read this section again.
This package provides support for copying data between a client and server. It allows a client to access an arbitrary set of fields of a record that exists on the server.
pvCopy supports the complete fieldDef syntax described above, i. e. it supports:
This is a facility used to monitor changes in a record. It uses the pvCopy facility and also provides two additional features:
The request argument is ignored.
The request string has the form:
"record[options]field(fieldOptions)"
An example of a record option is process=true. The fieldOptions are as defined by pvCopy.
The request string has the form:
"record[options]putField(fieldOptions)getField(fieldOptions)"
An example of a record option is process=true. The fieldOptions are as defined by pvCopy.
pvIOCJava has a set of records with associated support that implement a remote procedure model intended to be accessed via channelPutGet. These records have a substructure arguments and a substructure result. The client is expected to use the request string:
"record[process=true]putField(arguments)getField(result)"
The details of arguments and result depend on the support. The client makes a putGet request by setting values in arguments and gets the result from result.
An example is a record that has support for getting a string array where each element is the name of a record in the IOC. The argument has a single subfield named regularExpression, which is a grep style string. An example record is:
record laptoprecordListPGRPC structure arguments string regularExpression .*Double structure result string status success string[] names [scalarDouble,arrayDouble]
In this case the client gave an expression .*Double which found all records with names that ended with Double. For this IOC there were two records scalarDouble and arrayDouble.
pvIOCJava has only a simple example of support for channelRPC, which does not use pvRequest.
channelRPC is mostly used to implement services. Most services use normative types for both the argument passed to request and for the structure returned by a request. For example a NTNameValue for the argument and NTTable for the result. Thus createRequest will not normally be used by channelRPC services.
The request string has the form:
"field(fieldDef)"
FieldDef must refer to a subfield of the top level structure that has type scalarArray or structureArray. It it does not then an error is returned to the client.
The request string has the form:
"record[options]field(fieldOptions)"
options and fieldOptions are those defined above for monitor.
NOTE: pvaSrv is under development by Ralph Lange. Thus this is just some comments about how pvRequest could be handled by pvaSrv. Details will change. For example Ralph already has plans to rename multiChannel and change it's functionality.
pvaSrv currently has two pvAcccess servers: v3Channel and multiChannel.
This is a pvAcccess server for accessing records in an EPICS base IOC. A channel is a single V3 record. v3Channel supports all the Channel create methods except ChannelPutGet and ChannelRPC. For the others it supports the pvIOC Data Model. Thus a pvRequest can be created via createRequest as described above. A V3 record has a "flat" structure so it appears as a structure of the following type:
structure <someType> value structure alarm structure timeStamp structure display structure control
The v3Channel implemantation takes care of all conversions from the way V3 records are implemented and the pcIOC Data model.
Since this is a work in progress only a brief comment will be made. The support could be implemented so that it supports the pvIOCData Model, i. e. so that the client can use createRequest to create pvRequest structures. It is not clear if this is desirable. It may be better to provide custom support for creating the pvRequest structures that are easier for clients to use.
NOTE: This section is only of interest to someone who is implementing code that has to introspect a structure generated by createRequest.
pvRequest is a PVStructure that describes 1) record options and 2) field requests and options. It has the following structure:
structure structure record structure _options option ... structure field structure fieldName structure _options option ... structure fieldName structure _options option ... ... structure putField structure fieldName structure _options option ... structure fieldName structure _options option ... ... structure getField structure fieldName structure _options option ... structure fieldName structure _options option ... ...
where
Note:
Two examples of options are process and shareData.
Process is a record option:
structure structure record structure _options string process true
For example if process is an option to createGet then the record will be processed before data is fetched. NOTE: scalarType boolean is also supported.
The following is an example of a field option:
structure structure field structure value structure _options string shareData true
This is a request to share the actual data rather than creating a copy.
Some examples are:
request structure request alarm,timeStamp,power.value structure structure alarm structure timeStamp structure power structure value request record[process=true]field(alarm,timeStamp,power.value) structure structure record structure _options string process true structure field structure alarm structure timeStamp structure power structure value request record[process=true]field(alarm,timeStamp[algorithm=onChange,causeMonitor=false],power{value,alarm}) structure structure record structure _options string process true structure field structure alarm structure timeStamp structure _options string algorithm onChange string causeMonitor false structure power structure value structure alarm request record[process=true,xxx=yyy]field(alarm,timeStamp[shareData=true],power.value) structure structure record structure _options string process true string xxx yyy structure field structure alarm structure timeStamp structure _options string shareData true structure power structure value
This section describes the Java implementation of pvCopy, which is package org.epics.pvioc.pvCopy. It is implemented in pvIOCJava.html.
This package provides support for copying PVData between a client and server. For example the client can be a pvAccess client and the server a pvAccess server. It allows a client to access an arbitrary set of fields of a PVRecord that exists on the server. This package does not provide support for transfering the data between client and server but provides support for code that does, e. g. it can be used by channel access. In the examples below the data transfer code is called channel access.
This package uses the field offsets provided by PVField and also requires that user code creates a org.epics.pvdata.misc.BitSet.
PVField provides support for locating a field within a PVStructure or PVRecord via a field offset. PVField provides the methods:
A BitSet which has a bit for each field of a top level PVStructure can be created via:
BitSet bitSet = new BitSet(pvStructure.getNumberFields());
The offsets for bitSet match the fieldOffsets. On the client side of Channel Access only a BitSet created from the PVStructure is required. On the server side PVCopy provides a way to map between the field in a PVRecord and the fields in the PVStructure. A copy of the PVStructure resides on both the Channel Access client and server. The client and server exchange data via these two PVStructures. PVCopy maps between the PVStructure and the PVRecord on the server side of Channel Access.
This package provides the following:
This is the interface for mapping between a PVStructure that contain a copy of the data for a subset of the fields in a PVRecord. Note that this interface is NOT for a single PVStructure but for a single PVRecord and a single Structure introspection interface that describes a subset of the fields in the PVRecord. For example if a server supports monitor queues then the server will allocate a PVStructure for each queue element but will create a single PVCopy.
interface PVCopy { PVRecord getPVRecord(); Structure getStructure(); PVStructure createPVStructure(); int getCopyOffset(PVField recordPVField); int getCopyOffset(PVStructure recordPVStructure,PVField recordPVField); PVField getRecordPVField(int structureOffset); void initCopy(PVStructure pvCopy, BitSet bitSet,boolean recordLocked); void updateCopySetBitSet(PVStructure copyPVStructure,BitSet bitSet,boolean lockRecord); void updateCopyFromBitSet(PVStructure copyPVStructure,BitSet bitSet,boolean lockRecord); boolean updateRecord(PVStructure pvCopy,BitSet bitSet,boolean lockRecord); PVCopyMonitor createPVCopyMonitor(PVCopyMonitorRequester pvCopyMonitorRequester); PVStructure getOptions(PVStructure copyPVStructure,int fieldOffset); String dump(); }
where
PVCopyMonitor is a PVListener for the PVRecord to which PVCopy is attached. It updates two bitSets when it receives PVListener.dataPut callbacks. changeBit shows all fields that have changed between calls to switchBitSets. overrunBitSet shows all fields that have changed value more than once between calls to switchBitSets. It notifies the PVCopyMonitorRequester when data has changed.
interface PVCopyMonitorRequester { void dataChanged(); void unlisten(); } interface PVCopyMonitor { void startMonitoring(BitSet changeBitSet, BitSet overrunBitSet); void stopMonitoring(); void switchBitSets(BitSet newChangeBitSet,BitSet newOverrunBitSet, boolean lockRecord); }
PVCopyMonitorRequester is the interface implemented by the caller that calls PVCopy.createPVCopyMonitor:
PVCopyMonitor is the interface returned by a call to PVCopy.createPVCopyMonitor.
class PVCopyFactory { static PVCopy create(PVRecord pvRecord,PVStructure pvRequest,String structureName); }
create has the arguments
createRequest is an easy way to create a pvRequest structure to pass to create. It is described above.
This is a factory that replaces a scalar or scalarArray field with a version that shares the data from another field. When a get or put is issued the get or put method of the shared PVField is called.
class PVShareFactory { public static PVScalar replace(PVScalar pvNow,PVScalar pvShare); public static PVArray replace(PVArray pvNow,PVArray pvShare); }
This section describes the Java implementation of monitor, which is package org.epics.pvioc.monitor. It is implemented in pvIOCJava.html.
Package org.epics.pvdata.monitor defines the monitor interfaces as seen by a client. See that package overview for details. It is described in pvDataJava.html. This package implements the monitoring interfaces for a PVRecord.
The implementation uses PVCopy and PVCopyMonitor which are implemented in package org.epics.pvioc.pvCopy. When PVCopyMonitor tells monitor that changes have occurred, monitor applies the appropriate algorithm to each changed field and if any algorithm says to raise a monitor a monitor is sent to the client.
This package implements support for the following monitor algorithms: onPut, onChange, deadband, and periodic. Each is described in a subsection below.
In addition code for additional monitoring algorithms can be created and registered with the monitoring system.
onPut means that a monitor is issued whenever a put is issued to the field. This is the default unless the record defines a different algorithm for a field.
An exception is the top level timeStamp which by default is made onChange This is so that a monitor is not a monitor will not be raised if the only change to fields being monitored is the top level timeStamp.
This provides two options: 1) A monitor is raised whenever a field changes value, and 2) A monitor will never be raised for the field.
The client can ask for this option be asking for a field option that specifies the options algorithm and causeMonitor. An example is to pass the following request to createRequest."field(value[algorithm=onChange,causeMonitor=true])"
The default for causeMonitor is true so it only has to be specified if the client wants to specify false.
The field must be a numeric scalar. Whenever the absolute or percentage value of the field changes by more than a deadband then a monitor is issued.
The complete set of options for deadband are:
"field(value[algorithm=deadband,deadband=1.0,isPercent=true])"
The record instance can also define the default deadband. For example:
record example double value structure deadband structure display double value boolean isPercent // optional structure archive double value boolean isPercent // optional
I record will be monitored at a periodic rate if the pvRequest specifies a record option requisting it. A client can request this by passing the following request to createRequest
"record[periodicRate=rate]"
where
A monitor is issued at a periodic rate if a put was issued to any field being monitored.
This section discusses how to implement new monitor algorithms in addition to the onPut, onChange, etc, implemented by this package.
The monitor algorithm must register by calling:
MonitorFactory.registerMonitorAlgorithmCreater(MonitorAlgorithmCreate monitorAlgorithmCreate)
The implementation must implement interfaces MonitorAlgorithmCreate and MonitorAlgorithm which are described below.
This is a request to create a monitor.
class MonitorFactory { static Monitor create(PVRecord pvRecord, MonitorRequester monitorRequester,PVStructure pvRequest); static void registerMonitorAlgorithmCreater(MonitorAlgorithmCreate monitorAlgorithmCreate) }
where
The following must be implemented by code that implements a monitor algorithm.
interface MonitorAlgorithm { String getAlgorithmName(); boolean causeMonitor(); void monitorIssued(); }
where
The following must be implemented by code that implements a monitor algorithm.
interface MonitorAlgorithmCreate { String getAlgorithmName(); MonitorAlgorithm create( PVRecord pvRecord, MonitorRequester monitorRequester, PVField fromPVRecord, PVStructure pvOptions); }
where


You are viewing an historical archive of past issues. Please report new issues to the appropriate project issue tracker on GitHub.
Comments
-
Greg Hurrell
Sounds like miscompilation.
- What version of Ruby are you using and how did you get it?
- What's the full output of
ruby extconf.rb
andmake
when building Command-T? - What's the output of
ruby -v
(in the same directory as you build Command-T)? - Did you build MacVim from source or get it by other means?
- What's the output of
:version
and:ruby puts "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
in Vim?
-
Greg Hurrell
Closing due to lack of activity.
-
Greg Hurrell
Status changed:
- From: new
- To: closed
Add a comment
Comments are now closed for this issue.


REMINDER - Father's Day Free Pass | 6 Free Gifts | Ends Sunday
Visit Harbor Freight Tools:
Reminder - Father’s Day Free Pass | 6 Free Gifts | Ends Sunday, June 15th!
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5176949bef9e4ac2e05cf2d6837209dd6d3a2fe1650853f940c764486eb04e4ef
view this email online
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c54c5c1bdc33a2e07a75c9cf3050d17bd32695fed3d54e7be3ebaec4421b670b8f
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5700bf0a536c863eb4defc906bf33b822ede0c25d829d5cf96bc14c9b8427534e
Find a store near you
Shop
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5d5034a24887f79872ff5d03613a96879711d7d1b58802c37ea3491f87bf79d8d
www.HarborFreight.com or Call 1-800-423-2567 to Order
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c53d7aeeafb65a54fcd2878a1dc254726c7d5bbbcec335c610aae454490f9f5662
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c560d7b6336229ab8cc8d533c641f520ec4630152a97ebfa0fc4e955244934e990
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c520f2e1c7efafc6db7184659acfc197fe29fde57ec8830dd11f12f921459c276f
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c596178bf708910a0e1850862452fafe06575f05a605aa8cde18689abfbf3d8b75
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c52c6e067b1d2041038d75a0f7144f58e286fec93c60c4a3d54f6f6ab20d5aff14
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c599efc20d4ad8a463ed0500f2ba0e392220adcca4f5cbc46aed3afa6b3df1add2
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c58eda070514ab361b969b4aa7fe17f1728c588cef62c2597f9c13280d63cbebfd
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c50c2a526fa066bd4e518e233192986479cb0f917b532c58b10603316d3bd14369
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c51bf63ea60933268242d210ca25cb3756b7b74186e36d61bf55367caac166191b
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c554a0e824640fce372558684842237da05a977b89c131a9c36512ccc34aa21c9a
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5652f7a120683181bcdf7efaeaf4e3af81922c4009727da6044a798a6842bcfe6
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5167a9f1834c1500d915f1f5beaad12cc82f4b898ecd6f89cb5abef5378a96067
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5cd2de023a3e49dba12a0e2ff42ab4431d6e06267f3935beb426118661ab53d93
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5a49b9e4e13438f7547157962b78b1e1afc28648bfbcb81dd241a6e22cd4c83ac
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c59dcdfb66c7a15688ef25fb6b82cbdfdca60deb81275bf0feb55045b7232b985a
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c576e170ca7b0989de8357db4b2343e0bba35669ad453042b8a7b3612642ef491d
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c576e170ca7b0989de8357db4b2343e0bba35669ad453042b8a7b3612642ef491d
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c576e170ca7b0989de8357db4b2343e0bba35669ad453042b8a7b3612642ef491d
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c520f2e1c7efafc6db7184659acfc197fe29fde57ec8830dd11f12f921459c276f
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c52c6e067b1d2041038d75a0f7144f58e286fec93c60c4a3d54f6f6ab20d5aff14
Links
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5ea21fee2e75b0da82aba9fed76e1c361f522ba163bc77f0262cf6ff8dd4b4a9a
Customer Service
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5e3eb847e829e888162ab1806dc4def65cf59a97f6af6e8ec01ea93ed9e6bc7fe
Store Locator
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c59e7dcb8c18ef4d4e31c53e2126ff0ed4ea583f7195cc702652e7ddfc4f07429c
Current Ad
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5d2ccdd4eb508f2c6837a88c049b6c84f1c4e619b4149353ef4275ea81f8ebe8f
How to Use Coupons
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c51dac5c71fae8263b7fe9b7f5d3119a21d8a99671e9d4fec15c201c7340d3b7fa
Buy Gift Cards
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c597d083a4ee4fcd0951f9c78ccd3a63b843cfbbba08f669ce8edb6f9fe4c69479
Unsubscribe
Top Categories
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5ba7e3df3741634b37c65611aa1b6754dccff99e7b4db607cd46e1d020c10016c
Automotive/Motorcycle
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c52ef7d757cec2edde20501cfe629c368b020e3e44606b472c579e53dd47b990a5
Garage & Shop
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c57c9a8d58c6e1189aaf6279d2f931861db7124b861629c4f2f52888d6ac2a002a
Power Tools
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c577dcef6e2fc3f248d191bd974aac5f89f9ac0f99224f516d8e13fad156e4d546
Air Tools
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5f5a71474943e534c58bdfaf70ef64bd0141a3fef7c56d24ba0f65084729d32e3
Engines & Generators
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5e4c7a286b583ebfdee8f0ad5ea9f517e53139134fd5b15e852b9805f24b87d73
Hand Tools
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5e2113bc41fb2ee5ef749c1577f8f0228a7440df6c67592eb5aae8d1c309564a0
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c50d52554215fd8f48d914ce4c4664d90700008bec9ea50bd4ab333709a93dc786
Shop
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5d5034a24887f79872ff5d03613a96879711d7d1b58802c37ea3491f87bf79d8d
www.HarborFreight.com or Call 1-800-423-2567 to OrderStay Connected with HFT :
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c55e6fd87bbf7f3ce9c51bdfd11bea57766afb4ed44341f6e7d3f72be98d232885
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5c41453e9fd37784b13bda5d4e54ef0eaa990953d853fc92061e4c00adc05cd54
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5957c0fd4fa52ed467cad4f523b8a27276223313ef86505ed4af95c3d10e523e6
http://click.harborfreightemail.com/?qs=c8aa3427de71f7c5e1f9c556fc3749e74afaa93111f41a6ffca2bf9e9be9f7c9d329322061140b3b
PRICES- Although we make every effort to assure that our prices, products and coupons are advertised as accurately as possible, we are only human, and in the event an error is made, we reserve the right to correct it. To receive the coupon prices from this email online, the coupon code(s) must be entered into your shopping cart.
Harbor Freight Tools | 26541 Agoura Road | Calabasas, CA 91302 | 1-800-444-3353


To submit an Issue, you have to be a Participant in the AnkhSVN project. Request to be a Participant.
Submitting an Issue
Before you file a new issue, please:
What the Issue Fields Mean
When an issue is first filed, it automatically goes in the "---" target milestone, which indicates that the issue has not been processed yet. A developer will examine it and maybe talk to other developers, then estimate the bug's severity, the effort required to fix it, and schedule it in a numbered milestone, for example 1.1. (Or they may put it the unscheduled or nonblocking milestone, if they consider it tolerable for all currently planned releases.)
An issue filed under "unscheduled" might still get fixed soon,
if a committer or patch contributor decides he or she wants it
done. Putting it in unscheduled merely means it hasn't been
scheduled for any particular release yet. The nonblocking
milestone, on the other hand, means that we do not anticipate ever
scheduling the issue for a particular release. This also does not
mean the issue will never be fixed; it merely means that we don't
plan to block any release on it.
Severity is represented in the Priority field. Here is how
priority numbers map to severity:


--- layout: default title: Blog Posts ---
Hi there. I've been moving things around, and these are the archives of my blog posts pre-2013.
I am a software engineer/manager, filmmaker and photographer (in that order). For anything, you can reach me via anil.verve (@at) gmail.com.
The blog archives here.


This guide is meant to supplement the afternoon session of day 1 of the 2022 MESA Summer School at the University of California, Santa Barbara in August 2022. The labs were designed by Bill Wolf and Ebraheem Farag. The science case is loosely based on simulations in Wolf et al. 2013.
If you are completing this tutorial after the official summer school, you should use the following versions of MESA and the SDK:
These exercises may work or be adapted to older or newer releases, but there is no such expectation for them to work.
The primary purpose of this lab is to get you more familiar with some topics in MESA beyond the absolute basics, including:
We'll do all this while studying the topic of rapidly-accreting white dwarf stars. Even if this topic is of little scientific interest to you, it will be a useful playground in which to learn the skills above.
Throughout this guide, monospaced text on a black background, like this:
echo "Hello, World!" represent commands you can type into your
terminal and execute, while red monospaced text like this:
mass_change = 1d-9
represent file names or values that you might
type into a file, but not something to be executed.
Recurrent novae are thermonuclear flashes on the surfaces of rapidly-accreting massive white dwarfs. They are similar to classical novae, but they have been observed in outburst multiple times. In general, the more massive a white dwarf is, the shorter the recurrence time (time between successive flashes) will be. Similarly, the higher the accretion rate Ṁ, the shorter the recurrence time will be.
There is a limit to how high Ṁ can be before the nova will find an equilibrium solution and burn material at the same rate it is accreted. There is some debate as to whether this could physically occur (perhaps the accretion doesn't return after the initial flash or is never stable enough over long enough timescales, etc.). For now, we will assume that accretion resumes immediately after the initial "explosion", and we will see that stable burning does occur for sufficiently high Ṁ in MESA.
Our main goal is to find the transition Ṁ where this occurs, and we'll see that we need to be careful with our simulation if we want a consistent result.
In this first minilab, we'll get a MESA project up and running that we will use for the rest of the labs. You'll learn/practice the following skills:
The test suites in MESA star (located in $MESA_DIR/star/test_suite
), MESA binary (located in $MESA_DIR/binary/test_suite
), and MESA astero (you get the idea) help detect bugs in development by checking that the behavior of a known application doesn't change substantially as commits are made. They are also useful as starting points for your own projects. We'll do that in this exercise.
Challenge 1.1: Copy the wd_nova_burst
test case from MESA star into a new directory outside of the installation, and call it wolf
.
Assuming your MESA_DIR
is set up correctly, execute the
following in the terminal
Though you may choose to place wolf
somewhere other than
your home directory.
cp is a command that takes two arguments. The first is a source file, and the second is the target. It copies the source file to the target file, creating the target if necessary. The -r option means to do this recursively, so we can then use cp -r to copy entire directories.
Because this is a test case, some inlists, makefiles, and executable scripts will set their own MESA_DIR
variable such that the test case will ignore whatever you have set as your environment variable. This makes the test case work perfectly fine in its normal home, even if you haven't set MESA_DIR
, but outside of that location, it wreaks havoc. We need to fix this.
Challenge 1.2: Delete all
assignments to MESA_DIR
or mesa_dir
from the test case.
The command-line tool grep lets you hunt for text in files. The general way it works is something like
grep SEARCH_STRING FILE_GLOB.
Here, SEARCH_STRING is the text or pattern you are searching for, and FILE_GLOB is some pattern of files to search. As a more concrete (and useful) example, try executing the following:
This should show two instances where mesa_dir
is set:
So at the very least, we need to delete some lines in these two inlist files.
For even more power, grep lets you search recursively (search an entire directory, and any directories within it) with the -r
option, or in a case-insensitive search with the -i
options. Go ahead and give the following a try:
This searches all files in the current directory, including files inside of enclosed directories, for mesa_dir in a case-insensitive manner.
Remember, we only want to comment out or delete lines that assign mesa_dir/MESA_DIR. We don't want to delete lines that simply use it.
Look in the following for any lines that set MESA_DIR
or mesa_dir
and comment out or delete them:
make/makefile
inlist_setup_header
inlist_wd_nova_burst_header
rn
If you don't do this, compilation will look in the wrong place for your
installation, and/or the star
executable won't be able to find
the right data at runtime.
Comment out or delete the following lines:
make/makefile
: line 2inlist_setup_header
: line 4inlist_wd_nova_burst_header
: line 4rn
: line 5
There's another line in ck
, but that file is part of the
testing infrastructure, so we can ignore it.
As mentioned before, the test cases are set up this way so that, even if
you have your own version of MESA_DIR
, they can compile and
run properly in their normal home (e.g. star/test_suite/wd_nova_burst
). In later releases, this may
be changed, and you may not need to make these adjustments.
Each test case is unique, but as you experiment with them, you'll notice several trends.
In this test case, we have a whopping five files that star with the word inlist
. What's going on with this? Before diving into those, it first makes sense to look at the rn
script. Open this file up again and look at it (it's also shown below). It is not the same as the typical rn
script in the standard MESA work directory.
The most interesting thing here are the source
and two do_one
statements. You can look at $MESA_DIR/star/test_suite/test_suite_helpers
to see what is being hidden here, but the short of it is that it defines the function do_one
that we use afterwards. This function takes in an inlist and an optional model file name (in the first case, inlist_setup_header
and ready.mod
) and does a few things with them:
inlist
, overwriting it.
So in rn
, we're first running inlist_setup_header
, then checking to make sure that the model it creates exists before going on to run inlist_wd_nova_burst_header
. Through the copying action of the do_one
function, we never directly interact with the file inlist
. But why are there two inlists for each state (the header and the non-header versions)?
Open up inlist_setup_header
(also shown below) and inspect it.
So all this inlist does is point to the "real" inlist. Why do we do this? Well, we don't have to, but in this way, there's only one "true" inlist. Even if you interrupt the run and then restart it, you can make changes to inlist_setup
rather than directly to inlist
(which is what you'd have to do if we directly copied inlist_setup
to inlist
).
So now we can see why there are so many inlist-like files in this project. There are really only two that matter (so far): inlist_setup
and inlist_wd_nova_burst
. These correspond to two phases of the test that require different controls. Some test cases will have only one stage, and thus only one "real" inlist, while others can have five or more. For our purposes, inlist_setup
is not particularly interesting; it does some housekeeping before the main show, which is in inlist_wd_nova_burst
.
Test cases are designed to fail quickly if something goes wrong. This can mean that there are aggressive restrictions on age, model numbers, total retries, cumulative errors in energy, or other quantities. This is useful for testing purposes, but often not useful when modifying a test case, which will necessarily "break" it.
Challenge 1.3: Remove
aggressive stopping condition(s) in inlist_wd_nova_burst
.
We will tune this model to go into a steady burning mode so we can let it run for a very long time. There are one or more settings in the controls
namelist that will prevent this. See if you can figure out which one(s) they are and comment them out or delete them.
The biggest issue here is the limit on the maximum model number:
There are also a couple of controls dealing with the limit and hard limit for errors in energy coonservation. These won't end the run, though they may shrink timesteps in the interest of accurate energy conservation. They can stay.
Another option might have been min_timestep_limit
. However,
this is set to 1d-12
, which means that if the timestep dips below 10–12 seconds, the simulation will end, and that's probably a good idea!
Finally: it's not a problem, but you might also consider deleting/commenting out the three lines dealing with tracing relative energy error. This will declutter your terminal output since we don't really care about that in this project.
In addition to controls in the inlists, we should also look to run_star_extras.f90
to see if there is a custom stopping condition, since many test cases employ such things. In fact, there may be many unexpected things in this file, so it's always a good idea to check it out.
Open up src/run_star_extras.f90
. We won't analyze everything that's here (most is basically boilerplate code in every simulation). Note, though, the following lines near the top:
These define variables that the simulation uses in some of the other functions/subroutines. The first two variables are defined at the beginning of the run in extras_startup
, unless we are doing a restart, in which case they are loaded from the photo:
And all four are used in extras_check_model
:
Wherever you see s%
, that is accessing data in the stellar model (s
is called the "star info structure"; more on this later). Basically these controls then say that if we are waiting for a burst and the photometric luminosity exceeds 104 L⊙, then we have entered a burst, and when it then goes below 103 L⊙, we have finished the burst. After one such burst, the simulation is ended.
We want to give the simulation two bursts to determine if it is "stable" rather than just one. Also, since the accretion rate is so rapid, sometimes the luminosity doesn't drop low enough between bursts to be counted as finishing the first burst, so we need to make some changes to this code.
Challenge 1.4: Edit
extras_check_model
so the simulation ends after two bursts rather
than one.
In src/run_star_extras.f90
, edit the line
to
Challenge 1.5:
Edit run_star_extras.f90
so that a burst "ends" after the
luminosity drops below 5 × 103 L⊙ instead of
1 × 103 L⊙.
In src/run_star_extras.f90
, edit the line
to
Check that your solution compiles by executing ./mk.
With your modified test case working, let's move on to tweaking the inlists.
So far, we haven't really substantively changed anything in the test case other than modifying the stopping condition. Now we want to actually change how it works. We'll start by increasing the accretion rate, lowering the spatial resolution,
Challenge 1.6:
Edit inlist_wd_nova_burst
and/or inlist_setup
to make the following changes:
mesh_delta_coeff
to 3.0 (this is bad practice for science, but it will make simulations run faster)
basic.net
(another bad idea scientifically, but this will also help speed up the runs)
&pgstar
namelist is active and visible
None of these changes will require adding new lines to either inlist; you can do this all by editing or uncommenting existing lines.
The control you want to change is mass_change
in the controls
namelist.
Remember, you can learn all about different controls by looking at the files in $MESA_DIR/star/defaults
or via the
online reference.
In both inlist_setup
and inlist_wd_nova_burst
in the &controls
namelist, set mesh_delta_coeff = 3.0
. This is more important in inlist_wd_nova_burst
, but you can do it in inlist_setup
, too. These controls exist in both, but they are set to 1.0 initially.
The network is only set in inlist_setup
, and is left alone in inlist_wd_nova_burst
. There is a line in its &star_job
namelist that sets the name of the network, and you should edit it to "basic.net"
.
Interestingly, the control you want here is not in the pgstar
namelist! It's the pgstar_flag
control in the star_job
namelist. That line should already be there; you just need to uncomment it.
The controls in the &pgstar
namelist define what type of
plots will be displayed, but the pgstar_flag
is the main
switch that toggles plots on and off. More on what's in the
&pgstar
namelist in Minilab 2
In the &star_job
namelist in inlist_wd_nova_burst
uncomment the line
to become
And lower, in the &controls
namelist of inlist_wd_nova_burst
, the accretion rate
and spatial resolution can be updated by editing the lines
to
Finally, we update the nuclear network by editing inlist_setup
. Specifically, we change the following line in the &star_job
namelist:
to
Our simulation is almost ready to run, but there's one issue. We are loading a white dwarf model that was prepared to accrete at 10–9 M⊙/yr, but now we want it to accrete at a rate 350 times higher! While this might work, it would be a painful process requiring very short timesteps to allow the outer regions of the model to adjust to the new scenario. We'd like a way to slowly ramp up the accretion rate. Fortunately, MESA has just that sort of thing: relaxation routines.
Challenge 1.7: Search the
star_job
defaults for a set of controls that will allow us to
slowly increase the accretion rate from zero to 3.5 × 10–7
M⊙ over fifty timesteps, taking no more than 0.1 years per
timestep. Set these controls in inlist_wd_nova_burst
.
Within the MESA source, you can always look inside (or
grep into)
$MESA_DIR/star/defaults/star_job.defaults
. Alternatively, you
can find documentation generated from this file at
docs.mesastar.org.
Inlist defaults are detailed in the "Reference" section.
The primary flag you want to set is called
relax_initial_mass_change
, which you should set to
.true.
. There are an additional four numerical controls you
will also want to set.
You might also notice the very similar relax_mass_change
(missing the word "initial"). This will do the same thing, except it will
also trigger the relaxation process on restarts. We don't want to
do that; we only want to relax the accretion rate when we first start the
run, and then subsequent restarts can just go straight to the final
accretion rate.
Add the following lines to the star_job
namelist of
inlist_wd_nova_burst
:
Challenge 1.8: Get your model running! Compile if you haven't already, and then start the simulation.
After completing the setup inlist and the relaxation process, a pgstar window should open up. This may take a few minutes, but once it does, that means your simulation should be ready to run continuously. You may let it run or end the process once the pgstar window has appeared.
If you haven't already, compile your project with ./mk. Then get things started with ./rn. If you get any errors, go back and review the previous challenge solutions and look for any discrepancies with your files.
The rn
script will first run off of
inlist_setup_header
, which shouldn't take more than a minute.
Then, it should run off of inlist_wd_nova_burst_header
,
which should launch a pgstar window something like what is shown below.
This simulation should run continuously (and likely slowly). You can let it run continuously until the luminosity and effective temperature are pretty consistent at roughly log L/L⊙ ≈ 4.3 and log Teff ≈ 5.8. At this point, you can stop the run with CTRL-C.
That's the end of this first minilab, but we'll pick up where we left off in the next one. If you weren't able to get your project working properly, or you're skipping ahead to minilab 2, download the zip file below, which has a complete working directory that need only be compiled and ran.
This lab focuses on getting you more comfortable working with and customizing pgstar plots. In particular, you will:
pgstar is a built-in feature of MESA that allows for real-time graphical insight into how your model is evolving. While the plots it generates are usually not suitable for publication, being able to "see" your model evolve can be an invaluable tool in developing intuition and diagnosing issues. Additionally, you can easily string frames of pgstar output into a movie after a simulation, which is great for presentations and group meetings!
pgstar is comprised of several building blocks that we can sort into several [slightly-overlapping] categories.
history.data
, and the resolution will depend on how often data
is written to history.data.
profile.columns
since they do not need to persist over multiple
steps.
Most often, you'll deal with a grid or dashboard that contains many individual single- or multi-panel plots and/or text summaries arranged into a single window. We'll explore this next.
Look inside inlist_wd_nova_burst
, towards the bottom. You'll find
the pgstar
namelist, which is responsible for defining the
output you saw at the end of the last minilab. The relevant contents are
shown below for convenience.
The top line is the most important. This tells MESA to display
a pre-built grid called Grid8
, which is defined in
$MESA_DIR/star/defaults/pgstar.defaults
. Open that file up and
search for the section that defines Grid8
.
Here you can see how the grid is defined. We provide a width and aspect ratio
for the window, essentially defining its size. The various
Grid8_xleft
, Grid8_ybot
controls set up margins to
keep the contents from overflowing the window. Then the grid is set up by
setting numbers of rows, columns, and plots, as well as default values for the
names, positions, and padding for these plots.
After setting the defaults for all of these values, they are set to more
reasonable values, and each plot of the grid is defined. For
Grid8
in particular, there are 3 columns, 14 rows, and a total of
6 plots. How is this possible? Well, we can set individual plots to span
multiple columns or rows, so the total number of plots should always be less
than the product of the number of rows and columns.
Now let's look at the settings for one of the constituent plots:
All of these quantities are arrays, and we are setting the value at index 1,
meaning this is what we will refer to as "plot 1". The name
(Summary_Burn) refers to pre-defined plot found elsewhere in
pgstar.defaults
with many of its own settings. The rest of the
controls define which row and column to start in, and how many rows/columns
the plot should cover, and then some padding to make sure the plot doesn't
overflow its bounds.
After each of the plots are defined similarly, there is a small collection of controls governing file output. In addition to or instead of displaying live on-screen plots, pgstar can save a .png file for each timestep (or in this case, every fifth timestep, since the interval is set to five). If you want to make movies out of pgstar output, these controls will help you generate the individuall frames you'll need.
By looking at the various values in the Grid8_plot_name
array,
we can see that this grid contains the following plots:
As mentioned earlier, each of these plots can also be customized. In
the &pgstar
namelist of inlist_wd_nova_burst
, we
see a couple of lines that modify Summary_Burn by changing what
quantity is used for the horizontal axis as well as reversing it. You can see
how several of the other plots are tweaked as well, including changing a
couple of values in the text summary.
Now that we understand a bit more about how the pgstar display is being generated… let's throw it away and start with a fresh one!
In this part, we'll replace the dashboard that came with the test case with a custom (but incomplete) one. Download the file below and move it into your work directory (wolf).
Now that you have our new inlist_pgstar
in the work directory,
let's tell inlist_wd_nova_burst_header
to use it.
Challenge 2.1:
Edit inlist_wd_nova_burst_header
(and
not
inlist_wd_nova_burst
) so that it reads pgstar
controls from inlist_pgstar
rather than
inlist_wd_nova_burst
, and then copy the new header to
inlist
.
Note that the copying to inlist
is only necessary because we
will restart from a photo rather than starting a fresh run. If we were going
to rely on rn
, it would have done the copying for us.
Edit inlist_wd_nova_burst_header
from
to this:
Once that is done, overwrite inlist
via
cp inlist_wd_nova_burst_header inlist.
Now the &pgstar
namelist in inlist_wd_nova_burst
will never be used. Instead, we will use the one in
inlist_pgstar
. This is another reason why header style
inlists can be very useful; we can reuse inlists for particular purposes
and even share them between projects.
Again, the copying is necessary since if we did nothing else,
inlist
would still point to
inlist_wd_nova_burst_header
for all namelists until
rn
overwrote it or we did it by hand.
Challenge 2.2:
Resume your run by restarting from
the most recent photo. Do not start
from the beginning with the rn
script.
Simply execute ./re. When called without an explicit photo, it will default to using the most recent photo.
You can also provide a specific
photo corresponding to one found in the photos
directory, as
in ./re x150, which would restart from the last saved photo
from a model number ending in 150.
If all is working properly, you should see something like the dashboard shown below:
inlist_pgstar
on the fly
Unlike the other namelists, &pgstar
can be edited and saved
during a live run, and changes will [usually] take effect on the next
timestep. Assuming you don't mind your computer chugging on this
simulation, you can leave it running for the rest of this minilab. If you
make a bad change that causes the run to crash, just restart from a photo
again.
Go ahead and look inside inlist_pgstar
now and see if you can
understand how this works. Note that all three plots in the profile panels
are "special" profile plots (abundance, power, and mixing). The middle tall
plot is a panel of history plots, though at present, it contains only one
plot showing the evolution of central temperature and density with respect to
the model number. The bottom shows a text summary with some entries left
blank, and then along the left are the more familiar HR diagram and
temperature-density profile.
Our primary goals will be to
Let's start by adding information to the text summary, which is a little
more straightforward. Text summary fields can be any value that can be
included in a history file, so you can find the options in $MESA_DIR/star/defaults/history_columns.list
.
Challenge 2.3: Add the following to the text summary in the specified columns, below any existing entries:
Save your results and confirm that the changes are live on your dashboard.
Edit the following lines in inlist_pgstar
:
to the following:
Each of these new entries can be found in history_columns.list
.
Even if they are commented out there, they can be displayed in a text
summary.
And now, let's turn our attention to the history panels, which we will expand to a stack of three plots showing quantities of interest.
Challenge 2.4: Update the history panels to have three plots. All quantities should be plotted against the star age with a maximum width of 30 years. From top to bottom, the plots should show:
Save your results and confirm that the changes are live on your dashboard.
We are editing settings for the History_Panels1
plot. Find
these in inlist_pgstar
. You'll find three collections
of settings that reset the values of each panel to defaults and set the
x-axis to be star age.
The control History_Panels1_num_panels
sets the number of
panels in this plot. Set this to 3 to display three plots.
To set the main (left axis) data to plot, you need to set
History_Panels_1_yaxis_name(n)
to some string corresponding
to a history column. Here, n is some integer from 1 up to the
number of panels.
To set the other (right axis) data to plot, you need to set
History_Panels_1_other_yaxis_name(n)
(for some integer
n) to another string
corresponding to a history column.
At least one of these outputs is not currently output to
history.data
. Unlike text summaries, data for history plots
must be available in history.data
because past data
only exists in this file for plotting.
As a result, you will need to uncomment this in
history_columns.list
. Furthermore, you will need to delete (or at
least rename) your current history.data
file since
MESA will not be able to write to the file with a new column
inserted in the middle. After you've done this, you can restart from the
most recent photo again with ./re.
The History_Panels1
section of inlist_pgstar
should have the following:
Notably, the History_Panels1_num_panels
was set to 3,
the three groups below it were uncommented, the value of
History_Panels1_max_width
was set to 30 (instead of 2).
Finally, the six axes were set by setting various elements of
History_Panels1_yaxis_name
and
History_Panels1_other_yaxis_name
.
You may also have chosen to use photosphere_L
instead of log_L
for the first panel. In that case, you would also need to set History_Panels1_yaxis_log(1) = .true.
. If you went that route, then you would also need to uncommment the appropriate line in history_columns.list
, as indicated below.
To get the log_L_div_Ledd
panel to play nicely, we also had
to uncomment that column in history_columns.list
:
After doing this, you'll need to stop your run (CTRL-C), delete
your current history.data
(rm LOGS/history.data),
and then restart from a photo (./re) for that change to take
effect. Read Hint 2.4d for a more detailed discussion of this.
This is looking better, but chances are that most of your history panels are really ragged, even though the model is not changing a whole lot. Perhaps this raggedness is actually an issue, depending on how resolved you need the simulation to be, but for our purposes, we should not be this zoomed in. We'll zoom out to more reasonable limits on the y-axis now.
Challenge 2.5: Update the history panels to have the following limits on their y-axes:
Save your results and confirm that the changes are live on your dashboard.
The History_Panels1
section of inlist_pgstar
should now include the following (in addition to what was there before):
After making these changes, you should see that all the lines in the history panel should be much more horizontal.
Suppose you also wanted to track the mass fractions of hydrogen and helium at
the point of peak nuclear energy generation. You can look for a column like
this in history_columns.list
, but no such column exists. We will have
to add some of our own code to accomplish this. Here's a quick summary of how
this is done inside src/run_star_extras.f90
:
how_many_extra_history_columns
function to the number of extra columns you want to add.
names
in the subroutine data_for_extra_history_columns
.
vals
in the subroutine data_for_extra_history_columns
.
In step 3, you will likely need to use the star
info structure, which is usually represented with the letter
s
. You can use this to get at data stored in the stellar model.
For instance, s% m
yields an
array of the mass coordinates of the
star, and s% nz
gives the [scalar] total number of zones in the
model. Most useful members of the star info structure are defined in
$MESA_DIR/star_data/public
, particularly in
star_data_step_input.inc
and
star_data_step_work.inc
.
Challenge 2.6:
Edit src/run_star_extras.f90
to add two new history columns:
max_eps_nuc_X
: hydrogen mass fraction (X) at the point of
peak nuclear burning
max_eps_nuc_Y
: helium mass fraction (Y) at the point of peak
nuclear burning
Note that you will have to stop your run and recompile before these changes
can take effect. You should also delete your history.data
file
so the new columns can be properly added.
First, you need to tell MESA that you want to create two
columns. Do this by editing how_many_extra_history_columns
to:
Note in particular the second-to-last line, which has a 2 where there was originally a zero.
To make the first extra column, you'll need the line names(1) = 'max_eps_nuc_X'
inside data_for_extra_history_columns
.
You'll need something similar for the second column, but with a different
index and title.
While you can do this several ways, the simplest involves only three
members of the star info structure: eps_nuc
, X
,
and Y
.
If you find you need to loop over each cell, always set up your loop to
go from 1 up to s% nz
, rather than iterating over all the
elements in an array. This is because arrays in MESA are
not always resized, and may contain data beyond the current "last cell".
The trickiest part of computing the values for the columns is finding the
location of the maximum nuclear energy generation rate. Assuming you have
an array of these values, you could loop over these, keeping
track of the largest value you've found. Then you would update
vals(1)
and vals(2)
whenever
you come across a larger value.
But there's an easier way! Fortran provides a function that gives the
location of a maximum value of an array, which is called
MAXLOC
. The one wrinkle is that if all you do is give
MAXLOC
a one-dimensional array, it gives back a
one-dimensional array with only one element rather than a scalar. You can
get around this by using the second optional argument, which is the
dimension along which to find maxima, which in this case would be the
first (and only) dimension, so something like
MAXLOC(arr_of_eps_nuc, 1)
would give you the index where
arr_of_eps_nuc
is at a maximum.
First we have to set the number of new columns:
Then we need to set the names and values for the two new columns:
Alternatively, we could also use the loop strategy mentioned in hint 2.6d.
In this case, we need to define a real-valued variable to hold on to the
maximum value of the nuclear energy generation rate (we'll call it
max_eps_nuc
), and we also will need an integer to hold on
to the current cell number, which we'll call k
. We define
both of these in the fourth and fifth lines below, and then use them
both in the loop over zones.
Either way, we still need to compile with ./mk, then we delete the old history data with rm LOGS/history.data, and finally restart the run with ./re.
After restarting, you should see the new columns populating at the end of your
history.data
file. Now we can also use these in
pgstar.
Challenge 2.7: Fill in the last two entries of the second column of the text summary with the two new columns you just implemented. Confirm that they appear in your pgstar dashboard.
The following two lines should be added/edited in
inlist_pgstar
.
While they vary from step to step, you should find that the hydrogen mass fraction is around 0.2 while the helium mass fraction hovers a bit below 0.8.
When all is said and done, your pgstar dashboard should look quite similar to the one below.
Again, we will use this work directory for the maxilab, though the columns you may or may not have added in the previous section are not absolutely necessary, so don't worry if you didn't get to that. If you were stuck on something or just need to jump ahead to get to the maxilab, grab a completed directory with the download below.
We've seen that at sufficiently high accretion rates, a white dwarf can burn hydrogen-rich material at the same rate it is accreted, creating a stably and steadily burning model. These are observed as persistent supersoft sources (SSSs), since they are strong sources of supersoft X-rays.
Below some particular accretion rate, which we will call the stability boundary, the nuclear fusion becomes thermally unstable, and goes through a cycle of accretion and thermonuclear flashes known as recurrent novae. Below, a pgstar video shows a couple of recurrent novae resulting from lowering the accretion rate to 2.0 × 10–7 M⊙/yr.
Note how the model traces out a "swoosh" in the HR diagram, and there are two distinct bursts in the history panels, separated by about 25.5 years. We call this the recurrence time of the nova. Note also that we can see a time after the mass loss has ended that the luminosity and effective temperature are quite high. This is the supersoft source phase. It's harder to read off of the plots but it lasts from model 626 until the end (for the second burst), for duration of about 0.67 years.
Compare that with the behavior of the model you should have constructed in the previous two minilabs, run for roughly the same duration:
The model still appears to go through a "first flash" (you probably got through this in minilab 1), but it gets to the supersoft phase and just hangs there as the accreted material is consumed at the same rate it is accreted. Note that through many years, the H-rich layer mass is consistent at around 1.4 × 10–6 M⊙.
Our main goal in this lab is to determine more precisely the stability boundary for this 1.115 M⊙ white dwarf model.
Additionally, you should learn the following
run_star_extras.f90
In inlist_wd_nova_burst
, you should still have a line like
We've established that at this accretion rate, the model finds a steady and stable configuration where it fuses hydrogen at the same rate it is accreted. We also showed that at only 2 × 10–7, it enters a recurrent nova regime. Now we want to find the limiting accretion rate between these two regimes. We'll crowd-source random values between 2.0 × 10-7 M⊙/yr and 3.5 × 10-7 M⊙/yr. Pick yours with the tool below:
Challenge 3.1: Edit inlist_wd_nova_burst
so that the model accretes matter at the right Ṁ you chose.
Change the line above in inlist_wd_nova_burst
to mass_change = ???d-7
You should similarly update the following line in the
&star_job
namelist so that if you start the run over from
the beginning, that the relaxation method takes the accretion rate to the
right value:
To be clear, you should update this line from 3.5d-7
to your accretion rate of ???d-7
.
With your Ṁ selected, you should be able to resume your model. Restart the run with ./re.
Challenge 3.2: Restart your model, run it for at least 30 years if stable, or until it stops on its own if unstable, and report whether or not your model was stable.
If it was unstable, also report the following (try to get them to at least two significant digits from pgstar frames):
If it was stable, also report the hydrogen-rich layer mass
Here are some example videos showing unstable and stable configurations. Note that these start from the very beginning of the simulation; yours probably started from a hot an luminous state (near the left side of the "knee" in the HR diagram).
Here is a movie of a model that is clearly unstable:
And here is a movie of a model that is clearly stable:
The beginnings of flashes are the easiest to identify, so estimate the time between the two subsequent sharp rises in the luminosity to get the recurrence time.
The SSS phase duration is a little more nebulous. Estimate the the duration from the time of minimum effective temperature until the photospheric and hydrogen-burning luminosities precipitously depart from each other as they plummet. This may be too hard to do with a single pgstar frame, so consider making a movie out of your pgstar frames, and then find the frame when mass loss stops and roughly when the photospheric and nuclear luminosities begin to diverge.
The example below is taken from a simulation on a higher-mass white dwarf and higher accretion rate, so the timescales are much shorter. You should find recurrence times on the order of tens of years for the recurrence time and a little less than a year for the SSS phase duration.
This quantity should be on your pgstar dashboard. You added it to your panel in minilab 2 in the text summary.
Did you check out the hint about determining stability? The videos there (and earlier) were composed of all the output from pgstar. In this simulation, we output a snapshot of the pgstar output with each timestep into the png
directory. Take a look in there, and you'll see a png for every timestep.
To make these into a movie, the SDK comes with a nifty script that does the heavy lifting for you: images_to_movie
. To use it, do something like the following:
images_to_movie 'png/nova_grid*.png' my_movie.mp4
This takes all files that match the pattern nova_grid_*.png
in the png directory and then makes a movie by using each as a frame. The resulting movie is named my_movie.mp4
.
You can control how often pgstar outputs pngs, what they are named, and what directory they go into in the pgstar namelist.
Before you start a new run, delete all files in png
(rm -f png/*.png) or rename/move them. If you don't do this, you might end up with a mix of old and new files, and you won't know which is which.
This simulation is tuned to run quickly, and as a result, the supersoft phase doesn't last many timesteps. If we ran through it too quickly, we might actually have missed stability, while shorter timesteps might catch it. Our goal now is to modify our simulation so that it takes shorter time steps, but only during the SSS phase.
Fortunately, MESA has just the controls we want: delta_HR_limit
and delta_HR_hard_limit
. Both of these adjust the timestep based on how much the quantity
\[\eta = \sqrt{\left[\log (L/L_\odot)\right]^2 + \left[\log T_{\mathrm{eff}}\right]^2}\]
changes over the timestep. If η is greater than delta_HR_limit
, then the next timestep will be reduced by a factor of delta_HR_limit
/η. If instead η is greater than delta_HR_hard_limit
, the current timestep will be retried with a shorter timestep.
We could put these in our inlist, but they will make the entire run intolerably slow. We only want really high time resolution during the SSS phase, so we will implement this in run_star_extras.f90
, specifically, in extras_check_model
, which is executed at the end of every timestep. We want to use these tight tolerances only when the hydrogen burning luminosity is similar to the photospheric luminosity. We also only really care when the effective temperature is large.
Within subroutines in run_star_extras
, we can access the full data in the stellar model via the "star info structure", which is usually denoted as s
. All the data you can access are listed in various files in $MESA_DIR/star_data/public
. The two most useful files there are star_data_step_input.inc
and star_data_step_work.inc
. Additionally, inlist controls are members of the star info structure, so all controls in $MESA_DIR/star/defaults/controls.defaults
can be accessed and changed.
Depending on when in the timestep you access the star info structure, some values will have already been set and/or any changes you make might be overwritten by another internal subroutine. Understanding what is set when and how often boils down to grepping around in $MESA_DIR/star/private
. In extras_check_model
, everything should already be set, so we don't yet need to worry about this detail (but anything we change won't affect this timestep!).
To access a member of the star info structure, we just append a %
after s
, followed by the name. For instance, s% mstar
will return the baryonic mass of the stellar model in grams. Note that units in the star info structure are usually in cgs (unless otherwise indicated in the source file), while inlist controls are often given in solar units, so you may need to juggle conversions between the two.
Challenge 3.3: Edit extras_check_model
to add a conditional that sets delta_HR_limit
to 5d-3
and delta_HR_hard_limit
to 1d-2
whenever LH/Lphot < 1.1 and Teff > 500,000 K and when Lphot is above the burst threshold set by L_burst
. Otherwise both controls should be set to –1.
You will want to use the following in your conditional:
s% power_h_burn
: total power in L⊙ produced by hydrogen burning
s% L_phot
: photospheric luminosity in L⊙
s% Teff
: effective temperature in Kelvin
s%
means we are talking to the object that contains all the data about the stellar model, including physical quantities and inlist controls.
Use the following to set the timestep controls to the stricter limits:
and the looser limits:
The main conditional in extras_check_model
should now be something like this:
Note that the outer conditional takes care of the luminosity condition automatically.
Challenge 3.4: Recompile and rerun your model at the same Ṁ, but now with the increased time resolution. You can start the model from the beginning, or you can start from model 200 if the photo (x200
) is still handy and doesn't point to model 1200. Report the same quantities as before, but now on the second column of the google spreadsheet.
Now that we're better resolving the SSS phase (though we have not actually verified that this is converged spatially or temporally), we now turn our attention to the effects of the mass loss mechanism. Right now, we're using the super Eddington winds, which assume that all luminosity in excess comes in the form of mass moving at the escape speed:
$$L - L_{\rm Edd} = \frac{1}{2}\dot{M}v_{\rm esc}^2$$
Which yields a mass loss rate of:
$$\dot{M} = \frac{2(L-L_{\rm Edd})}{v_{\rm esc}^2}$$
How is that LEdd computed, though? Traditionally, it is
$$L_{\rm Edd} = \frac{4\pi GcM}{\kappa},$$
which is an entirely local quantity. Near the outside of the star, the mass is well-defined, but the opacity is rapidly changing. To make this even more unclear, this particular test case sets tau_factor
to 30, which means that the outermost cell is defined to be at an optical depth 30 times greater than the photospheric optical depth of 2/3, so at an optical depth of 20.
It turns out that, even with this definition, novae on this mass of WD are only super Eddington when their effective temperature just below the location of the bump in opacity due to iron at around log Teff = 5.2. If the nova expands beyond this, the model would simply keep expanding. We do see novae expanding to lower effective temperatures and still losing mass, so a more accurate treatment of mass loss is likely much more involved.
If mass loss is delayed or weaker, it may be the case that the post-outburst nova enters the SSS phase with a larger hydrogen-rich envelope and is able to accrete enough during this phase to resume steady burning, whereas stronger and earlier mass loss might cause too much of a reduction in the envelope, and erroneously manifest as a recurrent novae when perhaps it should manifest as a steady burner.
For illustrative purposes, we will make use of the control super_eddington_wind_Ledd_factor
, which is already set to 1 in inlist_wd_nova_burst
. As the comment indicates, this is a factor by which the Eddington luminosity is scaled by in computing the wind. We will increase this to 1.5 in the next challenge to make the winds start later and end sooner.
Challenge 3.5: Increase the value of super_eddington_wind_Ledd_factor
to 1.5. Either start your model from an early model (perhaps model 200 if photo x200
still points to that) or start your model over from the beginning (i.e. don't restart), and observe what happens during the initial flash. Depending on your accretion rate, it may get stuck in this phase. See if you can figure out what's going on.
Part of your inlist should look like
Restart your model from an early model like 200 (./re x200) or from the beginning with ./rn.
Depending on your accretion rate, you may see different outcomes, but many simulations will stall in the mass loss phase because the model oscillates wildly between accretion (mass gain) and winds (mass loss), so it cannot take very long timesteps.
In this video, see how the model has trouble getting through the mass loss phase? Part of this is because it is right at the cusp of triggering super Eddington winds, but the other issue is that whenever the winds cease, accretion immediately resumes (see the abundance plot for confirmation), which then affects the opacity in the outer layers, which triggers the winds, etc. In this case, we got out of it eventually, but these sorts of oscillations in behavior can spell doom for your simulation, and it's usually best to find a way to force a smoother transition.
Perhaps you ran into troubles in the last challenge during the mass loss phase. If not, see the video in the solution for an example of what could happen. As discussed in the solution, issues can arise when models oscillate wildly between mass loss and mass gain. To remedy this situation, we will try to stop accretion a little before mass loss would normally begin and resume it only a little after mass loss ends.
Perhaps we could do this by changing the mass_change
control on
the fly like we did with the delta_HR_limit
controls, but a more elegant solution is to use one of MESA's many other_*
hooks. These "hooks" let us plug bits of physics into the evolution loop at just the right moment. In our case, we want to intervene just after any winds have been calculated, but before any mass has been added or removed from the model. The other_adjust_mdot
hook allows for exactly this, letting us inject code at this critical time.
The process for implementing one of these hooks is usually pretty consistent:
$MESA_DIR/star/other
$MESA_DIR/star/other
into your run_star_extras.f90
extras_controls
subroutine in run_star_extras.f90
other_*
routine print something so we'll be able to see that it is being called
&controls
namelist to tell MESA to use your routine (usually the control is something like use_other_* = .true.
, where *
is standing in for something more specific)
Different hooks have different interfaces, so the above workflow may need a bit of tinkering to work right for you. We'll follow this process over the next couple of challenges for the other_adjust_mdot
hook.
Challenge 3.6: Implement the hook for other_adjust_mdot
up to step 7 (the hook shouldn't do anything interesting, but it should print something to show it is being called). Call the subroutine nova_adjust_mdot
.
The following code should be copied into run_star_extras.f90
below the contains
statement, but outside of any other subroutine or function:
Edit the two lines containing the text subroutine null_other_adjust_mdot
and edit it to subroutine nova_adjust_mdot
to get:
The quickest way to print something is to use something like write(*,*) "MY MESSAGE HERE"
.
You'll need to add the line s% other_adjust_mdot => nova_adjust_mdot
into
the extras_controls
subroutine, and then add the control use_other_adjust_mdot = .true.
to the controls namelist
of inlist_wd_nova_burst
.
The following should be in run_star_extras.f90
:
Note that the nova_adjust_mdot
subroutine doesn't have to be right after extras_controls
; it can be on its own anywhere else in the file, so long as it is below the contains
keyword. The only difference in extras_controls
is the line that sets s% other_adjust_mdot
.
Additionally, this bit should appear in inlist_wd_nova_burst
:
Then we compile (which takes into account the changes in run_star_extras.f90
) with ./mk and restart with ./re. You should see the message you printed in the terminal output for each step. In this case, Hello from nova_adjust_mdot
should appear.
We have successfully injected some code into our project at just the right time. Now we just need to make it useful! Look at our new nova_adjust_mdot
subroutine. There's not much going on. Some hooks work by having you modify data on the star info structure directly. Others work by having you modify a variable with an intent of out. This one doesn't really give you much to go on, and to be honest, the comments in $MESA_DIR/star/other/other_adjust_mdot.f90
don't really give you a whole lot to go on. So what can we do in here?
Well, if you track down when in the flow of execution s% other_adjust_mdot
is called in $MESA_DIR/star/private
, you'll find it is at the time when s% mstar_dot
has just been updated after winds. This is what we will want to [possibly] change in our routine.
But still, we don't even have the star info structure (what we usually call s
) set up yet! Many subroutines do this for us out of the box, so we might take it for granted. Look now at the extras_startup
subroutine (or one or many other subroutines, actually). This code is also shown below for convenience:
In that subroutine (and many others) there are three key lines that set up the star info structure. Early on, we instantiate a variable s
with a type of star_info
, then later we set it up by calling the subroutine star_ptr
, and for good measure, we return if the previous line set ierr
to an nonzero value (ierr
is an integer that indicates if an error has occurred when it is nonzero). After star_ptr
successfully exits, s
is now set up. We can do the same thing in nova_adjust_mdot
to set up the star info structure so we can query it for data and, crucially, update the value of s% mstar_dot
.
Challenge 3.7: Edit nova_adjust_mdot
to set up the star info structure, and then set s% mstar_dot
to zero if all of the conditions are met:
s% mstar_dot
is not currently negative (don't want to shut off winds)relax_initial_mass_change
; turns out we need this or else we'll shut off accretion even though the model thinks it is relaxing mass_change
)Confirm your code compiles, but don't run it until verifying that it makes sense by looking at the solution.
You can complete this challenge using only the three following members of the star info structure:
s% mstar_dot
s% Teff
s% doing_relax
Note that the model is considered "in a burst" whenever waiting_for_burst
is .false.
. You might as well reuse this nifty variable in your code!
You might also notice that there is another member, s% star_mdot
. Frustratingly, this is not what we want to use. If you have extra time, try to figure out what the difference between s% mstar_dot
and s% star_mdot
by grepping around in $MESA_DIR/star/private
.
Below is a working implementation. You may have come up with an equivalent solution. The MIN
function ensures that we don't override a negative value for s% mstar_dot
, but you could achieve the same effect with more conditionals.
Then confirm that it compiles with ./mk
Challenge 3.8: Test your model again with the functional nova_adjust_mdot
by recompiling and then restarting before the first burst gets to mass loss. Report the same data to the appropriate place in the google sheet. Keep the new value of super_eddington_wind_Ledd_factor
from Challenge 3.5.
Nothing should have changed for the beginning of your runs since minilab 2. You should be able to restart from several hundred steps in, so probably around photo x300
, assuming that corresponds with model 300 and not 1300 or 2300. You can do this via ./re x300. You can try different photos to see which start you during the thermonuclear runaway, but before the expansion and surge to the red.
If you can't find a good photo to start from, just start the model anew with ./rn. It'll just take longer (don't worry, this is the last one).
Here is an example of a model that was unstable in all sitautions before, but became stable with these modifications after the second burst. Note that the hiccups still occur during mass loss, but accretion doesn't resume (see the abundance plot for proof). Compare this to the video in Solution 3.5.
Again for your reference, a completed project is available for download below.
What happened to your model as we changed the resolution and the mass loss mechanism? There are probably three options:
At the high and low ends of the accretion rate range, you likely were in the first or second camp, but in the middle, some interesting things happen. So what is the "real" value for the stability limit? It's hard to say from this data alone, especially since we haven't truly confirmed that we have numerically converged by increasing the time resolution even further. In the interest of time, we never even tried increasing the spatial (mesh) resolution, but that should happen as well.
But even if this were numerically converged, our physical assumptions of mass loss can have a real effect. Perhaps the expansion of the atmosphere is strong enough to disrupt the accretion disc and the white dwarf could not resume accretion until much later, making steady burning much more difficult. Or perhaps a better model for mass loss would lose more or less of the envelope. At a certain point, you need to write the paper, in which case you should:
As the saying goes, "All models are wrong. Some are useful."


{% extends "base.html" %} {% block title %}Reset Password{% endblock %} {% block content %}
You are resetting your password for {{ reset.user.email }}.
Please choose your new password below.


Fizz reduces the price of its 1GB, 2GB and 4GB mobile plans
The changes will go into effect on November 22nd
by Aisha MalikFizz has confirmed to MobileSyrup that the telecommunications company has lowered the price of its 1GB, 2GB and 4GB mobile plans.
The company sent out emails to customers on November 21st notifying them of the change in prices.
A user on Reddit shared an email he received from the telecommunications company noting that the 1GB Canada-wide plan has been reduced from $30 per month to $26 per month.
Another user noted that the 4GB plan has been reduced from $44 per month to $37 per month.
The email notes that the price change is subject to go in effect on November 22nd and that the cost has already been updated in customers’ account.
It should be noted that it seems that the price has not changed for customers who are on beta or stabilization pricing.


Interesting application form 🙄
What is your name?
What is your favorite color?
Red
Green
Blue
Other
Would you like to become a developer?
Yes
Ano
Igen
是的
Why would you like to become a developer?
Submit


Navigation
Main Content
The first paragraph
The second paragraph
| Things | |||||
|---|---|---|---|---|---|
| Dog | Cat | ||||
| Properties | Fur | Black | Calico | ||
| Diet | Dead Rats | Live Rats | |||
| Hobbies | Chasing its tail | mauling rats | |||
| Summary | Neither animal is reliable |
The first paragraph
The second paragraph
| Things | |||||
|---|---|---|---|---|---|
| Dog | Cat | ||||
| Properties | Fur | Black | Calico | ||
| Diet | Dead Rats | Live Rats | |||
| Hobbies | Chasing its tail | mauling rats | |||
| Summary | Neither animal is reliable |